mirror of
https://github.com/wownero/lwsf-wow.git
synced 2026-10-17 07:21:28 +02:00
Added some more components, but still incomplete and only partially builds
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
BSD 3-Clause License
|
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
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are met:
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|||||||
+19
-13
@@ -425,22 +425,28 @@ struct WalletListener
|
|||||||
*/
|
*/
|
||||||
struct Wallet
|
struct Wallet
|
||||||
{
|
{
|
||||||
enum class Device {
|
enum Device {
|
||||||
Software = 0,
|
Device_Software = 0,
|
||||||
Ledger = 1,
|
Device_Ledger = 1,
|
||||||
Trezor = 2
|
Device_Trezor = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Status {
|
enum Status {
|
||||||
Ok,
|
Status_Ok,
|
||||||
Error,
|
Status_Error,
|
||||||
Critical
|
Status_Critical
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ConnectionStatus {
|
enum ConnectionStatus {
|
||||||
Disconnected,
|
ConnectionStatus_Disconnected,
|
||||||
Connected,
|
ConnectionStatus_Connected,
|
||||||
WrongVersion
|
ConnectionStatus_WrongVersion
|
||||||
|
};
|
||||||
|
|
||||||
|
enum BackgroundSyncType {
|
||||||
|
BackgroundSync_Off = 0,
|
||||||
|
BackgroundSync_ReusePassword = 1,
|
||||||
|
BackgroundSync_CustomPassword = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ~Wallet() = 0;
|
virtual ~Wallet() = 0;
|
||||||
@@ -938,7 +944,7 @@ struct Wallet
|
|||||||
virtual std::shared_ptr<AddressBook> addressBook() = 0;
|
virtual std::shared_ptr<AddressBook> addressBook() = 0;
|
||||||
virtual std::shared_ptr<Subaddress> subaddress() = 0;
|
virtual std::shared_ptr<Subaddress> subaddress() = 0;
|
||||||
virtual std::shared_ptr<SubaddressAccount> subaddressAccount() = 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
|
* \brief defaultMixin - returns number of mixins used in transactions
|
||||||
* \return
|
* \return
|
||||||
|
|||||||
+20
-2
@@ -29,8 +29,26 @@
|
|||||||
include_directories(.)
|
include_directories(.)
|
||||||
|
|
||||||
# For both the server and admin utility.
|
# 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_sources
|
||||||
set(monero-lwsf_headers backend.h lwsf_config.h rpc.h transaction_history.h transaction_info.h wallet.h)
|
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})
|
add_executable(monero-lwsf dummy_main.cpp ${monero-lwsf_sources} ${monero-lwsf_headers})
|
||||||
target_link_libraries(monero-lwsf
|
target_link_libraries(monero-lwsf
|
||||||
|
|||||||
+207
-96
@@ -31,8 +31,10 @@
|
|||||||
#include <boost/thread/lock_guard.hpp>
|
#include <boost/thread/lock_guard.hpp>
|
||||||
#include "cryptonote_basic/cryptonote_basic_impl.h" // monero/src
|
#include "cryptonote_basic/cryptonote_basic_impl.h" // monero/src
|
||||||
#include "lwsf_config.h"
|
#include "lwsf_config.h"
|
||||||
|
#include "ringct/rctOps.h"
|
||||||
#include "wire.h"
|
#include "wire.h"
|
||||||
#include "wire/adapted/crypto.h"
|
#include "wire/adapted/crypto.h"
|
||||||
|
#include "wire/adapted/pair.h"
|
||||||
#include "wire/msgpack.h"
|
#include "wire/msgpack.h"
|
||||||
#include "wire/wrapper/trusted_array.h"
|
#include "wire/wrapper/trusted_array.h"
|
||||||
|
|
||||||
@@ -81,24 +83,14 @@ namespace lwsf { namespace internal { namespace backend
|
|||||||
WIRE_FIELD(amount),
|
WIRE_FIELD(amount),
|
||||||
WIRE_FIELD(recipient),
|
WIRE_FIELD(recipient),
|
||||||
WIRE_FIELD(index),
|
WIRE_FIELD(index),
|
||||||
WIRE_FIELD(output_pub),
|
WIRE_OPTIONAL_FIELD(rct_mask),
|
||||||
WIRE_FIELD(rct_mask)
|
WIRE_FIELD(tx_pub)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
} // anonymous
|
||||||
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
|
|
||||||
{
|
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
template<typename F, typename T>
|
template<typename F, typename T>
|
||||||
void map_transaction(F& format, T& self)
|
void map_transaction(F& format, T& self)
|
||||||
{
|
{
|
||||||
@@ -113,27 +105,29 @@ namespace
|
|||||||
WIRE_OPTIONAL_FIELD(height),
|
WIRE_OPTIONAL_FIELD(height),
|
||||||
WIRE_FIELD(unlock_time),
|
WIRE_FIELD(unlock_time),
|
||||||
WIRE_FIELD(direction),
|
WIRE_FIELD(direction),
|
||||||
WIRE_OPTIONAL_FIELD(tx_secret),
|
|
||||||
WIRE_FIELD(id),
|
WIRE_FIELD(id),
|
||||||
|
WIRE_FIELD(prefix),
|
||||||
WIRE_FIELD(coinbase)
|
WIRE_FIELD(coinbase)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
} // anonymous
|
||||||
|
|
||||||
static void read_bytes(wire::reader& source, std::pair<crypto::hash, std::shared_ptr<transaction>>& dest)
|
static void read_bytes(wire::reader& source, std::pair<crypto::hash, std::shared_ptr<transaction>>& dest)
|
||||||
{
|
{
|
||||||
if (!dest.second)
|
if (!dest.second)
|
||||||
dest.second = std::make_shared<transaction>(transaction{});
|
dest.second = std::make_shared<transaction>();
|
||||||
read_bytes(source, *dest.second);
|
read_bytes(source, *dest.second);
|
||||||
dest.first = dest.second->id;
|
dest.first = dest.second->id;
|
||||||
}
|
}
|
||||||
static void write_bytes(wire::writer& dest, const std::pair<const crypto::hash, std::shared_ptr<transaction>>& source)
|
static void write_bytes(wire::writer& dest, const std::pair<crypto::hash, std::shared_ptr<transaction>>& source)
|
||||||
{
|
{
|
||||||
if (!source.second)
|
if (!source.second)
|
||||||
WIRE_DLOG_THROW(wire::error::schema::object, "Unexpected nullptr");
|
WIRE_DLOG_THROW(wire::error::schema::object, "Unexpected nullptr");
|
||||||
write_bytes(dest, *source.second);
|
write_bytes(dest, *source.second);
|
||||||
}
|
}
|
||||||
namespace {
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
template<typename F, typename T>
|
template<typename F, typename T>
|
||||||
void map_keypair(F& format, T& self)
|
void map_keypair(F& format, T& self)
|
||||||
{
|
{
|
||||||
@@ -148,12 +142,64 @@ namespace {
|
|||||||
wire::field("txes", wire::trusted_array(std::ref(self.txes))),
|
wire::field("txes", wire::trusted_array(std::ref(self.txes))),
|
||||||
WIRE_FIELD(type),
|
WIRE_FIELD(type),
|
||||||
WIRE_FIELD(view),
|
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.id = source.tx_hash;
|
||||||
out.timestamp = source.timestamp;
|
out.timestamp = source.timestamp;
|
||||||
out.fee = std::uint64_t(source.fee.value_or(rpc::uint64_string(0)));
|
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);
|
crypto::generate_key_image(output_pub, output_secret, image);
|
||||||
if (image == spend.key_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);
|
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)
|
if (!out)
|
||||||
throw std::logic_error{"nullptr transaction in merge_output"};
|
throw std::logic_error{"nullptr transaction in merge_output"};
|
||||||
|
|
||||||
out->prefix = output.tx_prefix_hash;
|
out->prefix = source.tx_prefix_hash;
|
||||||
const auto receive = out->receives.lower_bound(output.public_key);
|
update_output(out->receives.try_emplace(source.public_key).first->second, source, view_key);
|
||||||
if (receive == out->receives.end() || receive->first != output.public_key)
|
|
||||||
{
|
|
||||||
out->receives.emplace_hint(receive, output.public_key, transfer_in{output});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.scan_height = source.scanned_block_height;
|
||||||
self.blockchain_height = source.blockchain_height;
|
self.blockchain_height = source.blockchain_height;
|
||||||
self.primary.restore_height = source.start_height;
|
self.primary.restore_height = source.start_height;
|
||||||
|
|
||||||
self.primary.txes.reserve(source.transactions.size());
|
/* Backend server could remove or modify txes (rescan or bug fix); the
|
||||||
for (const auto& tx : source.transactions)
|
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 (tx.second)
|
||||||
if (iter == self.primary.txes.end() || iter->first != tx.tx_hash)
|
|
||||||
{
|
{
|
||||||
updated_wallet = true;
|
bool copy = false;
|
||||||
new_tx(self.primary, *self.primary.txes.emplace_hint(iter, tx.tx_hash, std::make_shared<transaction>(transaction{}))->second, tx);
|
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)
|
for (const auto& output : source.outputs)
|
||||||
{
|
{
|
||||||
auto iter = self.txes.find(output.tx_hash);
|
auto iter = self.primary.txes.find(output.tx_hash);
|
||||||
if (iter != self.txes.end())
|
if (iter != self.primary.txes.end())
|
||||||
merge_output(iter->second, output);
|
merge_output(iter->second, output, self.primary.view.sec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // anonymous
|
} // anonymous
|
||||||
@@ -253,39 +350,21 @@ namespace {
|
|||||||
void write_bytes(wire::writer& dest, const account& source)
|
void write_bytes(wire::writer& dest, const account& source)
|
||||||
{ map_account(dest, source); }
|
{ map_account(dest, source); }
|
||||||
|
|
||||||
transfer_in::transfer_in()
|
wallet::wallet()
|
||||||
: global_index(0),
|
: listener(nullptr),
|
||||||
amount(0),
|
client(),
|
||||||
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(),
|
|
||||||
primary{},
|
primary{},
|
||||||
status(),
|
|
||||||
last_sync(0),
|
last_sync(0),
|
||||||
scan_height(0),
|
scan_height(0),
|
||||||
blockchain_height(0),
|
blockchain_height(0),
|
||||||
sync(),
|
sync()
|
||||||
generated_locally(generated_locally)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
expect<epee::byte_slice> wallet::to_bytes() const
|
expect<epee::byte_slice> wallet::to_bytes() const
|
||||||
{
|
{
|
||||||
epee::byte_stream dest{};
|
epee::byte_stream dest{};
|
||||||
dest.reserve(config::initial_buffer_size);
|
dest.reserve(config::initial_buffer_size);
|
||||||
|
|
||||||
const boost::lock_guard<boost::mutex> lock{sync};
|
const boost::lock_guard<boost::mutex> lock{sync};
|
||||||
const std::error_code error = wire::msgpack::to_bytes(dest, primary);
|
const std::error_code error = wire::msgpack::to_bytes(dest, primary);
|
||||||
if (error)
|
if (error)
|
||||||
@@ -293,16 +372,13 @@ namespace {
|
|||||||
return epee::byte_slice{std::move(dest)};
|
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 boost::lock_guard<boost::mutex> lock{sync};
|
||||||
const std::error_code error = wire::msgpack::from_bytes(std::move(source), primary);
|
return wire::msgpack::from_bytes(std::move(source), primary);
|
||||||
if (error)
|
|
||||||
return error;
|
|
||||||
return success();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void wallet::refresh(const bool mandatory)
|
std::error_code wallet::refresh(const bool mandatory)
|
||||||
{
|
{
|
||||||
if (!mandatory)
|
if (!mandatory)
|
||||||
{
|
{
|
||||||
@@ -311,7 +387,7 @@ namespace {
|
|||||||
std::chrono::steady_clock::time_point::duration{last_sync.load()}
|
std::chrono::steady_clock::time_point::duration{last_sync.load()}
|
||||||
};
|
};
|
||||||
if (now - start < config::refresh_interval)
|
if (now - start < config::refresh_interval)
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
expect<rpc::get_unspent_outs> outs_response{common_error::kInvalidArgument};
|
expect<rpc::get_unspent_outs> outs_response{common_error::kInvalidArgument};
|
||||||
@@ -320,21 +396,56 @@ namespace {
|
|||||||
if (txs_response)
|
if (txs_response)
|
||||||
outs_response = rpc::invoke<rpc::get_unspent_outs>(client, login);
|
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)
|
if (!txs_response)
|
||||||
{
|
return txs_response.error();
|
||||||
status = txs_response.error();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!outs_response)
|
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();
|
const auto txid = epee::string_tools::pod_to_hex(tx->id);
|
||||||
return;
|
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{};
|
return {};
|
||||||
merge_response(*this, *txs_response);
|
}
|
||||||
merge_response(primary, *outs_response);
|
|
||||||
last_sync = std::chrono::steady_clock::now().time_since_epoch().count();
|
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
|
}}} // lwsf // internal // backend
|
||||||
|
|||||||
+51
-21
@@ -35,11 +35,13 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "byte_slice.h" // monero/contrib/epee/include
|
||||||
#include "common/expect.h" // moneor/src
|
#include "common/expect.h" // moneor/src
|
||||||
#include "crypto/crypto.h" // monero/src
|
#include "crypto/crypto.h" // monero/src
|
||||||
#include "crypto/hash.h" // monero/src
|
#include "crypto/hash.h" // monero/src
|
||||||
@@ -70,11 +72,7 @@ namespace lwsf { namespace internal { namespace backend
|
|||||||
crypto::public_key tx_pub;
|
crypto::public_key tx_pub;
|
||||||
|
|
||||||
transfer_out()
|
transfer_out()
|
||||||
: address(), amount(0), sender(), secret(), tx_pub{}
|
: 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)
|
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
WIRE_DECLARE_OBJECT(transfer_out);
|
WIRE_DECLARE_OBJECT(transfer_out);
|
||||||
@@ -85,19 +83,49 @@ namespace lwsf { namespace internal { namespace backend
|
|||||||
std::uint64_t amount;
|
std::uint64_t amount;
|
||||||
rpc::address_meta recipient;
|
rpc::address_meta recipient;
|
||||||
std::uint16_t index;
|
std::uint16_t index;
|
||||||
crypto::public_key output_pub;
|
std::optional<rct::key> rct_mask;
|
||||||
rct::key rct_mask;
|
crypto::public_key tx_pub;
|
||||||
|
|
||||||
transfer_in();
|
transfer_in()
|
||||||
explicit transfer_in(const rpc::output& source);
|
: global_index(0),
|
||||||
|
amount(0),
|
||||||
|
recipient{},
|
||||||
|
index(0),
|
||||||
|
rct_mask(),
|
||||||
|
tx_pub{}
|
||||||
|
{}
|
||||||
};
|
};
|
||||||
WIRE_DECLARE_OBJECT(transfer_in);
|
WIRE_DECLARE_OBJECT(transfer_in);
|
||||||
|
|
||||||
struct transaction
|
struct transaction
|
||||||
{
|
{
|
||||||
transaction() = delete;
|
transaction()
|
||||||
std::vector<transfer_out> spends;
|
: spends(),
|
||||||
std::map<crypto::public_key, transfer_in, memory> receives;
|
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 description;
|
||||||
std::string label;
|
std::string label;
|
||||||
std::optional<std::time_t> timestamp;
|
std::optional<std::time_t> timestamp;
|
||||||
@@ -107,8 +135,6 @@ namespace lwsf { namespace internal { namespace backend
|
|||||||
std::uint64_t unlock_time;
|
std::uint64_t unlock_time;
|
||||||
TransactionInfo::Direction direction;
|
TransactionInfo::Direction direction;
|
||||||
std::variant<std::nullptr_t, crypto::hash8, crypto::hash> payment_id;
|
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 id;
|
||||||
crypto::hash prefix;
|
crypto::hash prefix;
|
||||||
bool coinbase;
|
bool coinbase;
|
||||||
@@ -132,26 +158,30 @@ namespace lwsf { namespace internal { namespace backend
|
|||||||
NetworkType type;
|
NetworkType type;
|
||||||
keypair view;
|
keypair view;
|
||||||
keypair spend;
|
keypair spend;
|
||||||
|
bool generated_locally;
|
||||||
};
|
};
|
||||||
WIRE_DECLARE_OBJECT(account);
|
WIRE_DECLARE_OBJECT(account);
|
||||||
|
|
||||||
struct wallet
|
struct wallet
|
||||||
{
|
{
|
||||||
|
std::shared_ptr<WalletListener> listener;
|
||||||
rpc::http_client client;
|
rpc::http_client client;
|
||||||
account primary;
|
account primary;
|
||||||
std::error_code status;
|
std::atomic<std::chrono::steady_clock::time_point::duration::rep> last_sync;
|
||||||
std::atomic<std::chrono::steady_clock::time_point::rep> last_sync;
|
|
||||||
std::uint64_t scan_height;
|
std::uint64_t scan_height;
|
||||||
std::uint64_t blockchain_height;
|
std::uint64_t blockchain_height;
|
||||||
|
std::uint64_t per_byte_fee;
|
||||||
|
std::uint64_t fee_mask;
|
||||||
mutable boost::mutex sync;
|
mutable boost::mutex sync;
|
||||||
const bool generated_locally;
|
|
||||||
|
|
||||||
explicit wallet(bool generated_locally);
|
wallet();
|
||||||
|
|
||||||
expect<epee::byte_slice> to_bytes() const;
|
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;
|
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
|
}}} // lwsf // internal // backend
|
||||||
|
|||||||
+73
-12
@@ -33,8 +33,10 @@
|
|||||||
#include <boost/spirit/include/qi_parse_attr.hpp>
|
#include <boost/spirit/include/qi_parse_attr.hpp>
|
||||||
#include <boost/spirit/include/qi_sequence.hpp>
|
#include <boost/spirit/include/qi_sequence.hpp>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include "hex.h" // monero/contrib/epee/include
|
||||||
#include "lwsf_config.h"
|
#include "lwsf_config.h"
|
||||||
#include "net/http_client.h" // monero/contrib/epee/include
|
#include "net/http_client.h" // monero/contrib/epee/include
|
||||||
|
#include "ringct/rctOps.h" // monero/contrib/epee/include
|
||||||
#include "wire.h"
|
#include "wire.h"
|
||||||
#include "wire/adapted/crypto.h"
|
#include "wire/adapted/crypto.h"
|
||||||
#include "wire/field.h"
|
#include "wire/field.h"
|
||||||
@@ -71,14 +73,14 @@ namespace lwsf { namespace internal { namespace rpc
|
|||||||
struct category final : std::error_category
|
struct category final : std::error_category
|
||||||
{
|
{
|
||||||
virtual const char* name() const noexcept override final
|
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
|
virtual std::string message(int value) const override final
|
||||||
{
|
{
|
||||||
return get_string(error(value));
|
return get_string(error(value));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
static const category instance{};
|
static const category instance{};
|
||||||
return instance;
|
return instance;
|
||||||
@@ -95,7 +97,7 @@ namespace lwsf { namespace internal { namespace rpc
|
|||||||
return {error::invoke_failure};
|
return {error::invoke_failure};
|
||||||
if (!response)
|
if (!response)
|
||||||
return {error::invoke_failure};
|
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 {error::wrong_response_code};
|
||||||
|
|
||||||
return response->m_body;
|
return response->m_body;
|
||||||
@@ -149,7 +151,7 @@ namespace lwsf { namespace internal { namespace rpc
|
|||||||
using min_spent = wire::min_element_sizeof<crypto::key_image>;
|
using min_spent = wire::min_element_sizeof<crypto::key_image>;
|
||||||
|
|
||||||
std::optional<std::string> timestamp;
|
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::object(source,
|
||||||
WIRE_FIELD_ARRAY(spent_outputs, min_spent),
|
WIRE_FIELD_ARRAY(spent_outputs, min_spent),
|
||||||
wire::optional_field("payment_id", std::ref(payment_id)),
|
wire::optional_field("payment_id", std::ref(payment_id)),
|
||||||
@@ -159,10 +161,10 @@ namespace lwsf { namespace internal { namespace rpc
|
|||||||
WIRE_FIELD(unlock_time),
|
WIRE_FIELD(unlock_time),
|
||||||
WIRE_OPTIONAL_FIELD(height),
|
WIRE_OPTIONAL_FIELD(height),
|
||||||
WIRE_FIELD(tx_hash),
|
WIRE_FIELD(tx_hash),
|
||||||
WIRE_FIELD(is_coinbase)
|
WIRE_FIELD(is_coinbase),
|
||||||
|
WIRE_FIELD(mempool)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if (payment_id && !payment_id->empty())
|
if (payment_id && !payment_id->empty())
|
||||||
{
|
{
|
||||||
if (payment_id->size() == sizeof(crypto::hash8))
|
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)
|
void read_bytes(wire::json_reader& source, output& self)
|
||||||
{
|
{
|
||||||
|
wire::basic_value raw_rct{};
|
||||||
wire::object(source,
|
wire::object(source,
|
||||||
WIRE_FIELD(amount),
|
WIRE_FIELD(amount),
|
||||||
WIRE_FIELD(index),
|
WIRE_FIELD(index),
|
||||||
WIRE_FIELD(global_index),
|
WIRE_FIELD(global_index),
|
||||||
WIRE_OPTIONAL_FIELD(recipient),
|
WIRE_OPTIONAL_FIELD(recipient),
|
||||||
WIRE_FIELD(rct),
|
wire::optional_field("rct", std::ref(raw_rct)),
|
||||||
WIRE_FIELD(tx_hash),
|
WIRE_FIELD(tx_hash),
|
||||||
WIRE_FIELD(tx_prefix_hash),
|
WIRE_FIELD(tx_prefix_hash),
|
||||||
WIRE_FIELD(public_key),
|
WIRE_FIELD(public_key),
|
||||||
WIRE_FIELD(tx_pub_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)
|
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)
|
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
|
}}} // lwsf // internal // rpc
|
||||||
|
|
||||||
|
|||||||
@@ -30,17 +30,17 @@
|
|||||||
|
|
||||||
#include <boost/variant.hpp>
|
#include <boost/variant.hpp>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <optional>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include "byte_slice.h" // monero/contrib/epee/include
|
#include "byte_slice.h" // monero/contrib/epee/include
|
||||||
#include "byte_stream.h" // moneor/contrib/epee/include
|
#include "byte_stream.h" // moneor/contrib/epee/include
|
||||||
#include "common/expect.h" // monero/src
|
#include "common/expect.h" // monero/src
|
||||||
#include "crypto/crypto.h" // monero/src
|
#include "crypto/crypto.h" // monero/src
|
||||||
#include "ringct/rctTypes.h" // monero/src
|
#include "ringct/rctTypes.h" // monero/src
|
||||||
|
#include "wire/basic_value.h"
|
||||||
#include "wire/fwd.h"
|
#include "wire/fwd.h"
|
||||||
#include "wire/json.h"
|
#include "wire/json.h"
|
||||||
|
|
||||||
// Contains code for interact
|
|
||||||
|
|
||||||
namespace epee { namespace net_utils
|
namespace epee { namespace net_utils
|
||||||
{
|
{
|
||||||
class blocked_mode_client;
|
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
|
inline std::error_code make_error_code(const error value) noexcept
|
||||||
{
|
{
|
||||||
return std::error_code{int(value), error_category()};
|
return std::error_code{int(value), error_category()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//! Send `payload` to `client` at uri `endpoint`, and \return payload response
|
//! Send `payload` to `client` at uri `endpoint`, and \return payload response
|
||||||
@@ -84,7 +84,7 @@ namespace lwsf { namespace internal { namespace rpc
|
|||||||
F out{};
|
F out{};
|
||||||
error = wire::json::from_bytes(std::move(*result), out);
|
error = wire::json::from_bytes(std::move(*result), out);
|
||||||
if (error)
|
if (error)
|
||||||
return out;
|
return error;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,9 +123,10 @@ namespace lwsf { namespace internal { namespace rpc
|
|||||||
|
|
||||||
std::optional<bool> generated_locally;
|
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
|
struct daemon_status
|
||||||
{
|
{
|
||||||
daemon_status() = delete;
|
daemon_status() = delete;
|
||||||
@@ -181,6 +182,7 @@ namespace lwsf { namespace internal { namespace rpc
|
|||||||
std::optional<std::uint64_t> height;
|
std::optional<std::uint64_t> height;
|
||||||
crypto::hash tx_hash;
|
crypto::hash tx_hash;
|
||||||
bool is_coinbase;
|
bool is_coinbase;
|
||||||
|
bool mempool;
|
||||||
|
|
||||||
transaction()
|
transaction()
|
||||||
: spent_outputs(),
|
: spent_outputs(),
|
||||||
@@ -191,7 +193,8 @@ namespace lwsf { namespace internal { namespace rpc
|
|||||||
unlock_time(0),
|
unlock_time(0),
|
||||||
height(),
|
height(),
|
||||||
tx_hash{},
|
tx_hash{},
|
||||||
is_coinbase(false)
|
is_coinbase(false),
|
||||||
|
mempool(false)
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
void read_bytes(wire::json_reader&, transaction&);
|
void read_bytes(wire::json_reader&, transaction&);
|
||||||
@@ -212,11 +215,18 @@ namespace lwsf { namespace internal { namespace rpc
|
|||||||
struct ringct
|
struct ringct
|
||||||
{
|
{
|
||||||
ringct() = delete;
|
ringct() = delete;
|
||||||
rct::key commitment;
|
|
||||||
|
enum class format : std::uint8_t
|
||||||
|
{
|
||||||
|
none = 0, //!< Not ringct
|
||||||
|
encrypted,
|
||||||
|
recompute,
|
||||||
|
unencrypted
|
||||||
|
};
|
||||||
|
|
||||||
rct::key mask;
|
rct::key mask;
|
||||||
rct::key amount;
|
format type;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(ringct) == 96);
|
|
||||||
|
|
||||||
struct output
|
struct output
|
||||||
{
|
{
|
||||||
@@ -254,6 +264,24 @@ namespace lwsf { namespace internal { namespace rpc
|
|||||||
std::uint64_t fee_mask;
|
std::uint64_t fee_mask;
|
||||||
};
|
};
|
||||||
void read_bytes(wire::json_reader&, get_unspent_outs&);
|
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
|
}}} // lwsf // internal // rpc
|
||||||
|
|
||||||
WIRE_DECLARE_BLOB_NS(lwsf::internal::rpc::ringct);
|
WIRE_DECLARE_BLOB_NS(lwsf::internal::rpc::ringct);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
#include "transaction_info.h"
|
#include "transaction_info.h"
|
||||||
|
|
||||||
#include "string_tools.h" // monero/contrib/epee/include
|
#include "hex.h" // monero/contrib/epee/include
|
||||||
|
|
||||||
namespace lwsf { namespace internal
|
namespace lwsf { namespace internal
|
||||||
{
|
{
|
||||||
@@ -59,17 +59,15 @@ namespace lwsf { namespace internal
|
|||||||
std::shared_ptr<TransactionInfo> transaction_history::transaction(int index) const
|
std::shared_ptr<TransactionInfo> transaction_history::transaction(int index) const
|
||||||
{
|
{
|
||||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||||
if (std::numeric_limits<int>::max() < data_->primary.txes.size())
|
if (index < 0 || data_->primary.txes.size() < unsigned(index))
|
||||||
throw std::runtime_error{"Exceeded max int size in transaction_history:transaction"};
|
|
||||||
if (data_->primary.txes.size() < index || index < 0)
|
|
||||||
throw std::runtime_error{"index provided to transaction invalid"};
|
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
|
std::shared_ptr<TransactionInfo> transaction_history::transaction(const std::string &id) const
|
||||||
{
|
{
|
||||||
crypto::hash binary_id{};
|
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"};
|
throw std::runtime_error{"transaction_history given invalid hex id"};
|
||||||
|
|
||||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||||
|
|||||||
@@ -54,17 +54,24 @@ namespace lwsf { namespace internal
|
|||||||
|
|
||||||
transfers_.reserve(data_->spends.size());
|
transfers_.reserve(data_->spends.size());
|
||||||
for (const auto& spend : data_->spends)
|
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()
|
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
|
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};
|
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
|
std::string transaction_info::hash() const
|
||||||
|
|||||||
@@ -55,13 +55,13 @@ namespace lwsf { namespace internal
|
|||||||
transaction_info& operator=(transaction_info&&) = delete;
|
transaction_info& operator=(transaction_info&&) = delete;
|
||||||
|
|
||||||
virtual Direction direction() const override { return data_->direction; }
|
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 isFailed() const override { return false; }
|
||||||
virtual bool isCoinbase() const override { return data_->coinbase; }
|
virtual bool isCoinbase() const override { return data_->coinbase; }
|
||||||
virtual uint64_t amount() const override { return data_->amount; }
|
virtual uint64_t amount() const override { return data_->amount; }
|
||||||
virtual uint64_t fee() const override { return data_->fee; }
|
virtual uint64_t fee() const override { return data_->fee; }
|
||||||
virtual uint64_t blockHeight() const override { return data_->height.value_or(0); }
|
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 std::set<uint32_t> subaddrIndex() const override { return {}; };
|
||||||
virtual uint32_t subaddrAccount() const override { return 0; }
|
virtual uint32_t subaddrAccount() const override { return 0; }
|
||||||
virtual std::string label() const override { return data_->label; }
|
virtual std::string label() const override { return data_->label; }
|
||||||
|
|||||||
+429
-24
@@ -28,16 +28,30 @@
|
|||||||
|
|
||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
|
|
||||||
|
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||||
|
#include <fcntl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream>
|
#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 "backend.h"
|
||||||
|
#include "error.h"
|
||||||
#include "hex.h" // monero/contrib/epee/include
|
#include "hex.h" // monero/contrib/epee/include
|
||||||
#include "lwsf_config.h"
|
#include "lwsf_config.h"
|
||||||
#include "net/parse.h" // monero/src
|
#include "net/parse.h" // monero/src
|
||||||
#include "net/socks_connect.h" // monero/src
|
#include "net/socks_connect.h" // monero/src
|
||||||
#include "transaction_history.h"
|
#include "transaction_history.h"
|
||||||
|
#include "wire.h"
|
||||||
|
#include "wire/msgpack.h"
|
||||||
|
|
||||||
namespace lwsf { namespace internal
|
namespace lwsf { namespace internal
|
||||||
{
|
{
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
struct null_connector
|
struct null_connector
|
||||||
@@ -48,29 +62,369 @@ namespace lwsf { namespace internal
|
|||||||
throw std::runtime_error{"Unable to connect"};
|
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)
|
void wallet::set_critical(const std::exception& e)
|
||||||
: data_(std::move(data)), filename_(std::move(filename)), thread_()
|
{
|
||||||
|
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_)
|
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()
|
wallet::~wallet()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void wallet::statusWithErrorString(int& status, std::string& errorString) const
|
int wallet::status() const
|
||||||
{
|
{
|
||||||
const boost::unique_lock<boost::mutex> lock{data_->sync};
|
int out = 0;
|
||||||
status = data_->status.value();
|
std::string unused;
|
||||||
if (status)
|
statusWithErrorString(out, unused);
|
||||||
errorString = data_->status.message();
|
return out;
|
||||||
else
|
|
||||||
errorString.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
std::string wallet::secretViewKey() const
|
||||||
{
|
{
|
||||||
@@ -94,22 +448,62 @@ namespace lwsf { namespace internal
|
|||||||
|
|
||||||
bool wallet::store(const std::string& path)
|
bool wallet::store(const std::string& path)
|
||||||
{
|
{
|
||||||
expect<epee::byte_slice> bytes = data_->to_bytes();
|
const std::string& real_path = path.empty() ? filename_ : path;
|
||||||
if (!bytes)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
//! \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};
|
epee::byte_stream buffer;
|
||||||
file.write(reinterpret_cast<const char*>(bytes->data()), bytes->size());
|
buffer.reserve(payload_size + 2048);
|
||||||
file.flush();
|
|
||||||
return file.good();
|
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)
|
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)
|
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(':');
|
const auto split = daemon_address.rfind(':');
|
||||||
if (!setProxy(proxy_address))
|
if (!setProxy(proxy_address))
|
||||||
@@ -148,7 +542,7 @@ namespace lwsf { namespace internal
|
|||||||
Wallet::ConnectionStatus wallet::connected() const
|
Wallet::ConnectionStatus wallet::connected() const
|
||||||
{
|
{
|
||||||
return data_->client.is_connected() ?
|
return data_->client.is_connected() ?
|
||||||
ConnectionStatus::Connected : ConnectionStatus::Disconnected;
|
ConnectionStatus_Connected : ConnectionStatus_Disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wallet::setProxy(const std::string &address)
|
bool wallet::setProxy(const std::string &address)
|
||||||
@@ -157,8 +551,7 @@ namespace lwsf { namespace internal
|
|||||||
if (!endpoint)
|
if (!endpoint)
|
||||||
{
|
{
|
||||||
data_->client.set_connector(null_connector{});
|
data_->client.set_connector(null_connector{});
|
||||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
set_error(endpoint.error());
|
||||||
data_->status = endpoint.error();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
data_->client.set_connector(net::socks::connector{std::move(*endpoint)});
|
data_->client.set_connector(net::socks::connector{std::move(*endpoint)});
|
||||||
@@ -177,9 +570,21 @@ namespace lwsf { namespace internal
|
|||||||
uint64_t wallet::daemonBlockChainTargetHeight() const
|
uint64_t wallet::daemonBlockChainTargetHeight() const
|
||||||
{ return blockChainHeight(); }
|
{ 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()
|
std::shared_ptr<TransactionHistory> wallet::history()
|
||||||
{
|
{
|
||||||
return std::make_shared<transaction_history>(data_);
|
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
|
}} // lwsf // internal
|
||||||
|
|||||||
+29
-11
@@ -30,7 +30,9 @@
|
|||||||
|
|
||||||
#include "lws_frontend.h"
|
#include "lws_frontend.h"
|
||||||
|
|
||||||
|
#include <boost/thread/mutex.hpp>
|
||||||
#include <boost/thread/thread.hpp>
|
#include <boost/thread/thread.hpp>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "cryptonote_basic/account.h" // monero/src
|
#include "cryptonote_basic/account.h" // monero/src
|
||||||
#include "net/http_client.h" // monero/contrib/epee/include
|
#include "net/http_client.h" // monero/contrib/epee/include
|
||||||
@@ -41,15 +43,28 @@ namespace internal
|
|||||||
{
|
{
|
||||||
namespace backend { struct wallet; }
|
namespace backend { struct wallet; }
|
||||||
|
|
||||||
|
|
||||||
//! \TODO Mark final when completely implemented
|
//! \TODO Mark final when completely implemented
|
||||||
class wallet : public lwsf::Wallet
|
class wallet : public lwsf::Wallet
|
||||||
{
|
{
|
||||||
const std::shared_ptr<backend::wallet> data_;
|
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_;
|
boost::thread thread_;
|
||||||
|
const std::uint64_t iterations_;
|
||||||
public:
|
mutable boost::mutex sync_;
|
||||||
explicit wallet(std::string filename, std::shared_ptr<backend::wallet> data);
|
|
||||||
|
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(const wallet&) = delete;
|
||||||
wallet(wallet&&) = delete;
|
wallet(wallet&&) = delete;
|
||||||
@@ -62,10 +77,12 @@ public:
|
|||||||
virtual std::string getSeedLanguage() const = 0;
|
virtual std::string getSeedLanguage() const = 0;
|
||||||
virtual void setSeedLanguage(const std::string &arg) = 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 void statusWithErrorString(int& status, std::string& errorString) const override;
|
||||||
|
|
||||||
virtual bool setPassword(const std::string &password) = 0;
|
virtual bool setPassword(const std::string &password) override;
|
||||||
virtual const std::string& getPassword() const = 0;
|
virtual const std::string& getPassword() const override { return password_; }
|
||||||
|
|
||||||
virtual bool setDevicePin(const std::string &) override { return false; };
|
virtual bool setDevicePin(const std::string &) override { return false; };
|
||||||
virtual bool setDevicePassphrase(const std::string &) override { return false; };
|
virtual bool setDevicePassphrase(const std::string &) override { return false; };
|
||||||
@@ -212,8 +229,8 @@ public:
|
|||||||
* @return - true if connected
|
* @return - true if connected
|
||||||
*/
|
*/
|
||||||
virtual ConnectionStatus connected() const override;
|
virtual ConnectionStatus connected() const override;
|
||||||
virtual void setTrustedDaemon(bool arg) = 0;
|
virtual void setTrustedDaemon(bool) override {}
|
||||||
virtual bool trustedDaemon() const = 0;
|
virtual bool trustedDaemon() const override { return true; }
|
||||||
virtual bool setProxy(const std::string &address) override;
|
virtual bool setProxy(const std::string &address) override;
|
||||||
virtual uint64_t balance(uint32_t accountIndex = 0) const = 0;
|
virtual uint64_t balance(uint32_t accountIndex = 0) const = 0;
|
||||||
uint64_t balanceAll() const {
|
uint64_t balanceAll() const {
|
||||||
@@ -499,7 +516,8 @@ public:
|
|||||||
* \brief disposeTransaction - destroys transaction object
|
* \brief disposeTransaction - destroys transaction object
|
||||||
* \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned;
|
* \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.
|
* \brief Estimates transaction fee.
|
||||||
@@ -507,7 +525,7 @@ public:
|
|||||||
* \return Estimated fee.
|
* \return Estimated fee.
|
||||||
*/
|
*/
|
||||||
virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations,
|
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
|
* \brief exportKeyImages - exports key images to file
|
||||||
@@ -549,7 +567,7 @@ public:
|
|||||||
virtual std::shared_ptr<AddressBook> addressBook() = 0;
|
virtual std::shared_ptr<AddressBook> addressBook() = 0;
|
||||||
virtual std::shared_ptr<Subaddress> subaddress() = 0;
|
virtual std::shared_ptr<Subaddress> subaddress() = 0;
|
||||||
virtual std::shared_ptr<SubaddressAccount> subaddressAccount() = 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
|
* \brief defaultMixin - returns number of mixins used in transactions
|
||||||
* \return
|
* \return
|
||||||
|
|||||||
+30
-8
@@ -28,11 +28,14 @@
|
|||||||
|
|
||||||
#include "lws_frontend.h"
|
#include "lws_frontend.h"
|
||||||
|
|
||||||
|
#include <boost/utility/string_ref.hpp>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include "backend.h"
|
#include "backend.h"
|
||||||
|
#include "common/dns_utils.h" // monero/src
|
||||||
#include "lwsf_config.h"
|
#include "lwsf_config.h"
|
||||||
#include "net/http_client.h" // monero/contrib/epee/include
|
#include "net/http_client.h" // monero/contrib/epee/include
|
||||||
#include "net/parse.h" // monero/src
|
#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
|
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>(
|
||||||
return std::make_unique<wallet>(path, std::make_shared<backend::wallet>(true));
|
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
|
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
|
* \param wallet previously opened / created wallet instance
|
||||||
* \return None
|
* \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)
|
if (wallet && store)
|
||||||
wallet->store(wallet->filename());
|
wallet->store(wallet->filename());
|
||||||
@@ -236,7 +244,7 @@ namespace lwsf
|
|||||||
*/
|
*/
|
||||||
bool walletExists(const std::string &path) override
|
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
|
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
|
//! checks for an update and returns version, hash and url
|
||||||
static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(
|
static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(
|
||||||
const std::string &software,
|
const std::string &software,
|
||||||
std::string subdir,
|
std::string subdir,
|
||||||
const char *buildtag = nullptr,
|
const char *buildtag = nullptr,
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
# 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.
|
# 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_sources basic_value.cpp 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_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})
|
add_library(monero-lws-wire ${monero-lws-wire_sources} ${monero-lws-wire_headers})
|
||||||
target_include_directories(monero-lws-wire PUBLIC "${LMDB_INCLUDE}")
|
target_include_directories(monero-lws-wire PUBLIC "${LMDB_INCLUDE}")
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
|
|
||||||
namespace wire
|
namespace wire
|
||||||
{
|
{
|
||||||
|
struct basic_value;
|
||||||
class reader;
|
class reader;
|
||||||
struct writer;
|
struct writer;
|
||||||
}
|
}
|
||||||
|
|||||||
+105
-74
@@ -25,17 +25,18 @@
|
|||||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
// 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.
|
// 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 <algorithm>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <rapidjson/memorystream.h>
|
#include <rapidjson/memorystream.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#include "common/expect.h" // monero/src
|
#include "byte_stream.h"
|
||||||
#include "hex.h" // monero/contrib/epee/include
|
#include "hex.h"
|
||||||
#include "wire/error.h"
|
#include "serialization/wire/basic_value.h"
|
||||||
#include "wire/json/error.h"
|
#include "serialization/wire/error.h"
|
||||||
|
#include "serialization/wire/json/error.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -50,8 +51,7 @@ namespace
|
|||||||
//! \throw std::system_error by converting `code` into a std::error_code
|
//! \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)
|
[[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 = 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 end = start + std::min(snippet_size, source.size() - start);
|
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};
|
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
|
case rapidjson::kParseErrorTermination: // the handler returned false
|
||||||
break;
|
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 json_reader::rapidjson_sax
|
||||||
{
|
{
|
||||||
struct string_contents
|
|
||||||
{
|
|
||||||
const char* ptr;
|
|
||||||
std::size_t length;
|
|
||||||
};
|
|
||||||
|
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
bool boolean;
|
bool boolean;
|
||||||
std::intmax_t integer;
|
std::intmax_t integer;
|
||||||
std::uintmax_t unsigned_integer;
|
std::uintmax_t unsigned_integer;
|
||||||
double number;
|
double number;
|
||||||
string_contents string;
|
|
||||||
} value;
|
} value;
|
||||||
|
|
||||||
error::schema expected_;
|
std::string* const string_;
|
||||||
|
const error::schema expected_;
|
||||||
|
error::schema actual_;
|
||||||
bool negative;
|
bool negative;
|
||||||
|
|
||||||
explicit rapidjson_sax(error::schema expected) noexcept
|
rapidjson_sax(error::schema expected) noexcept
|
||||||
: expected_(expected), negative(false)
|
: 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;
|
return expected_ == error::schema::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Bool(bool i) noexcept
|
bool Bool(bool i) noexcept
|
||||||
{
|
{
|
||||||
value.boolean = i;
|
value.boolean = i;
|
||||||
|
actual_ = error::schema::boolean;
|
||||||
return expected_ == error::schema::boolean || expected_ == error::schema::none;
|
return expected_ == error::schema::boolean || expected_ == error::schema::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,13 +121,14 @@ namespace wire
|
|||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
case error::schema::none:
|
||||||
case error::schema::integer:
|
case error::schema::integer:
|
||||||
value.integer = i;
|
value.integer = i;
|
||||||
|
actual_ = error::schema::integer;
|
||||||
break;
|
break;
|
||||||
case error::schema::number:
|
case error::schema::number:
|
||||||
value.number = i;
|
value.number = i;
|
||||||
break;
|
actual_ = error::schema::number;
|
||||||
case error::schema::none:
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -137,13 +139,14 @@ namespace wire
|
|||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
case error::schema::none:
|
||||||
case error::schema::integer:
|
case error::schema::integer:
|
||||||
value.unsigned_integer = i;
|
value.unsigned_integer = i;
|
||||||
|
actual_ = error::schema::integer;
|
||||||
break;
|
break;
|
||||||
case error::schema::number:
|
case error::schema::number:
|
||||||
value.number = i;
|
value.number = i;
|
||||||
break;
|
actual_ = error::schema::number;
|
||||||
case error::schema::none:
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -152,17 +155,20 @@ namespace wire
|
|||||||
bool Double(double i) noexcept
|
bool Double(double i) noexcept
|
||||||
{
|
{
|
||||||
value.number = i;
|
value.number = i;
|
||||||
|
actual_ = error::schema::number;
|
||||||
return expected_ == error::schema::number || expected_ == error::schema::none;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool String(const char* str, std::size_t length, bool) noexcept
|
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;
|
return expected_ == error::schema::string || expected_ == error::schema::none;
|
||||||
}
|
}
|
||||||
bool Key(const char* str, std::size_t length, bool)
|
bool Key(const char* str, std::size_t length, bool)
|
||||||
@@ -215,20 +221,59 @@ namespace wire
|
|||||||
read_next_value(accept_all);
|
read_next_value(accept_all);
|
||||||
}
|
}
|
||||||
|
|
||||||
json_reader::json_reader(std::string&& source)
|
std::size_t json_reader::do_start_array(std::size_t)
|
||||||
: reader(nullptr),
|
|
||||||
source_(std::move(source)),
|
|
||||||
reader_()
|
|
||||||
{
|
{
|
||||||
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
|
void json_reader::check_complete() const
|
||||||
{
|
{
|
||||||
if (depth())
|
if (depth())
|
||||||
WIRE_DLOG_THROW(error::rapidjson_e(rapidjson::kParseErrorUnspecificSyntaxError), "Unexpected end");
|
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()
|
bool json_reader::boolean()
|
||||||
{
|
{
|
||||||
rapidjson_sax json_bool{error::schema::boolean};
|
rapidjson_sax json_bool{error::schema::boolean};
|
||||||
@@ -264,24 +309,24 @@ namespace wire
|
|||||||
WIRE_DLOG_THROW_(error::schema::larger_integer);
|
WIRE_DLOG_THROW_(error::schema::larger_integer);
|
||||||
return static_cast<std::uintmax_t>(json_uint.value.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()
|
std::uintmax_t json_reader::safe_unsigned_integer()
|
||||||
{
|
{
|
||||||
if (get_next_token() != '"')
|
bool is_string = false;
|
||||||
WIRE_DLOG_THROW_(error::schema::string);
|
if (get_next_token() == '"')
|
||||||
remaining_.remove_prefix(1);
|
{
|
||||||
|
remaining_.remove_prefix(1);
|
||||||
|
is_string = true;
|
||||||
|
}
|
||||||
|
|
||||||
const std::uintmax_t out = unsigned_integer();
|
const std::uintmax_t out = unsigned_integer();
|
||||||
|
|
||||||
if (get_next_token() != '"')
|
if (is_string)
|
||||||
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorStringMissQuotationMark));
|
{
|
||||||
remaining_.remove_prefix(1);
|
if (get_next_token() != '"')
|
||||||
|
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorStringMissQuotationMark));
|
||||||
|
remaining_.remove_prefix(1);
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,38 +339,34 @@ namespace wire
|
|||||||
|
|
||||||
std::string json_reader::string()
|
std::string json_reader::string()
|
||||||
{
|
{
|
||||||
rapidjson_sax json_string{error::schema::string};
|
{
|
||||||
read_next_value(json_string);
|
rapidjson_sax json_string{str_buffer_, error::schema::string};
|
||||||
return std::string{json_string.value.string.ptr, json_string.value.string.length};
|
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();
|
const boost::string_ref value = get_next_string();
|
||||||
|
|
||||||
std::vector<std::uint8_t> out;
|
epee::byte_stream out{};
|
||||||
out.resize(value.size() / 2);
|
out.put_n(0, value.size() / 2);
|
||||||
|
|
||||||
if (!epee::from_hex::to_buffer(epee::to_mut_span(out), value))
|
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();
|
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))
|
if (!epee::from_hex::to_buffer(dest, value))
|
||||||
WIRE_DLOG_THROW(error::schema::fixed_binary, "of size" << dest.size() * 2 << " but got " << value.size());
|
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();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool json_reader::is_array_end(const std::size_t count)
|
bool json_reader::is_array_end(const std::size_t count)
|
||||||
@@ -348,21 +389,11 @@ namespace wire
|
|||||||
return false;
|
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)
|
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};
|
rapidjson_sax json_key{str_buffer_, error::schema::string};
|
||||||
const auto process_key = [map] (const rapidjson_sax::string_contents value)
|
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)
|
for (std::size_t i = 0; i < map.size(); ++i)
|
||||||
{
|
{
|
||||||
if (map[i].name == key)
|
if (map[i].name == key)
|
||||||
@@ -395,7 +426,7 @@ namespace wire
|
|||||||
|
|
||||||
// parse key
|
// parse key
|
||||||
read_next_value(json_key);
|
read_next_value(json_key);
|
||||||
index = process_key(json_key.value.string);
|
index = process_key(str_buffer_);
|
||||||
if (get_next_token() != ':')
|
if (get_next_token() != ':')
|
||||||
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorObjectMissColon));
|
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorObjectMissColon));
|
||||||
remaining_.remove_prefix(1);
|
remaining_.remove_prefix(1);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "wire/field.h"
|
#include "wire/field.h"
|
||||||
|
#include "wire/fwd.h"
|
||||||
#include "wire/json/base.h"
|
#include "wire/json/base.h"
|
||||||
#include "wire/read.h"
|
#include "wire/read.h"
|
||||||
#include "wire/traits.h"
|
#include "wire/traits.h"
|
||||||
@@ -63,6 +64,9 @@ namespace wire
|
|||||||
//! \throw wire::exception if JSON parsing is incomplete.
|
//! \throw wire::exception if JSON parsing is incomplete.
|
||||||
void check_complete() const override final;
|
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.
|
//! \throw wire::exception if next token not a boolean.
|
||||||
bool boolean() override final;
|
bool boolean() override final;
|
||||||
|
|
||||||
@@ -72,7 +76,7 @@ namespace wire
|
|||||||
//! \throw wire::exception if next token not an unsigned integer.
|
//! \throw wire::exception if next token not an unsigned integer.
|
||||||
std::uintmax_t unsigned_integer() override final;
|
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();
|
std::uintmax_t safe_unsigned_integer();
|
||||||
|
|
||||||
//! \throw wire::exception if next token not a valid real number
|
//! \throw wire::exception if next token not a valid real number
|
||||||
@@ -82,7 +86,7 @@ namespace wire
|
|||||||
std::string string() override final;
|
std::string string() override final;
|
||||||
|
|
||||||
//! \throw wire::exception if next token cannot be read as hex
|
//! \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`.
|
//! \throw wire::exception if next token cannot be read as hex into `dest`.
|
||||||
void binary(epee::span<std::uint8_t> dest) override final;
|
void binary(epee::span<std::uint8_t> dest) override final;
|
||||||
|
|||||||
@@ -413,11 +413,12 @@ namespace wire
|
|||||||
return std::string{reinterpret_cast<const char*>(bytes.data()), bytes.size()};
|
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();
|
update_tags_remaining();
|
||||||
const epee::span<const std::uint8_t> bytes = read_binary(remaining_, get_tag());
|
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)
|
void msgpack_reader::binary(epee::span<std::uint8_t> dest)
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ namespace wire
|
|||||||
std::string string() override final;
|
std::string string() override final;
|
||||||
|
|
||||||
//! \throw wire::exception if next token cannot be read as hex
|
//! \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`.
|
//! \throw wire::exception if next token cannot be read as hex into `dest`.
|
||||||
void binary(epee::span<std::uint8_t> dest) override final;
|
void binary(epee::span<std::uint8_t> dest) override final;
|
||||||
|
|||||||
+3
-3
@@ -32,8 +32,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
|
#include "byte_slice.h" // monero/contrib/epee/include
|
||||||
#include "common/expect.h" // monero/src
|
#include "common/expect.h" // monero/src
|
||||||
#include "span.h" // monero/contrib/epee/include
|
#include "span.h" // monero/contrib/epee/include
|
||||||
#include "wire/error.h"
|
#include "wire/error.h"
|
||||||
@@ -106,7 +106,7 @@ namespace wire
|
|||||||
virtual std::string string() = 0;
|
virtual std::string string() = 0;
|
||||||
|
|
||||||
// ! \throw wire::exception if next value cannot be read as binary
|
// ! \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`.
|
//! \throw wire::exception if next value cannot be read as binary into `dest`.
|
||||||
virtual void binary(epee::span<std::uint8_t> dest) = 0;
|
virtual void binary(epee::span<std::uint8_t> dest) = 0;
|
||||||
@@ -163,7 +163,7 @@ namespace wire
|
|||||||
{ dest = source.string(); }
|
{ dest = source.string(); }
|
||||||
|
|
||||||
template<typename R>
|
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(); }
|
{ dest = source.binary(); }
|
||||||
|
|
||||||
template<typename R, typename T>
|
template<typename R, typename T>
|
||||||
|
|||||||
Reference in New Issue
Block a user