Several big changes:

- Stubbed out incomplete components to get fully compilation
  - Added a (mostly) working ledger output as example usage
  - Fixed several minor bugs discovered during first usage
This commit is contained in:
Lee *!* Clagett
2025-04-01 05:35:25 -04:00
parent 8a0d23eabc
commit ce3f27b8ce
32 changed files with 1594 additions and 411 deletions
+10 -4
View File
@@ -27,21 +27,23 @@
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
include_directories(.)
add_subdirectory(wire)
# For both the server and admin utility.
set(monero-lwsf_sources
set(lwsf_sources
address_book.cpp
backend.cpp
error.cpp
pending_transaction.cpp
rpc.cpp
subaddress_account.cpp
subaddress_minor.cpp
transaction_history.cpp
transaction_info.cpp
wallet.cpp
wallet_manager.cpp
)
set(monero-lwsf_headers
set(lwsf_headers
backend.h
error.h
lwsf_config.h
@@ -52,9 +54,13 @@ set(monero-lwsf_headers
wallet.h
)
add_executable(monero-lwsf dummy_main.cpp ${monero-lwsf_sources} ${monero-lwsf_headers})
target_link_libraries(monero-lwsf
add_executable(lwsf-ledger ledger_main.cpp ${lwsf_sources} ${lwsf_headers})
target_link_libraries(lwsf-ledger
PRIVATE
lwsf-wire
lwsf-wire-json
lwsf-wire-msgpack
lwsf-wire-wrapper
monero::libraries
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
+64 -16
View File
@@ -29,6 +29,8 @@
#include "backend.h"
#include <boost/thread/lock_guard.hpp>
#include "crypto/crypto.h" // monero/src
#include "crypto/crypto-ops.h" // monero/src
#include "cryptonote_basic/cryptonote_basic_impl.h" // monero/src
#include "error.h"
#include "lwsf_config.h"
@@ -126,8 +128,7 @@ namespace lwsf { namespace internal { namespace backend
void map_sub_account(F& format, T& self)
{
wire::object(format,
wire::field("minor", wire::trusted_array(std::ref(self.minor))),
WIRE_FIELD(label)
wire::field("minor", wire::trusted_array(std::ref(self.minor)))
);
}
@@ -207,6 +208,7 @@ namespace lwsf { namespace internal { namespace backend
void map_account(F& format, T& self)
{
wire::object(format,
WIRE_FIELD(language),
wire::field("addressbook", wire::trusted_array(std::ref(self.addressbook))),
wire::field("subaccounts", wire::trusted_array(std::ref(self.subaccounts))),
wire::field("txes", wire::trusted_array(std::ref(self.txes))),
@@ -266,20 +268,34 @@ namespace lwsf { namespace internal { namespace backend
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)
crypto::secret_key get_spend_secret(const account& self, const std::optional<rpc::address_meta>& index)
{
if (!index || (index->maj_i == 0 && index->min_i == 0))
return self.spend.sec;
// m = Hs(a || index_major || index_minor)
const crypto::secret_key m = get_subaddress_secret_key(self.view.sec, index->maj_i, index->min_i);
// D = B + M
crypto::secret_key out;
sc_add(to_bytes(out), to_bytes(m), to_bytes(self.spend.sec));
return out;
}
bool 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.hash;
out.timestamp = source.timestamp;
out.fee = std::uint64_t(source.fee.value_or(rpc::uint64_string(0)));
out.height = source.height;
out.unlock_time = source.unlock_time;
out.payment_id = source.payment_id;
out.coinbase = source.is_coinbase;
out.coinbase = source.coinbase;
std::uint64_t total_spent = 0;
for (const auto& spend : source.spent_outputs)
@@ -291,7 +307,7 @@ namespace lwsf { namespace internal { namespace backend
if (!crypto::derive_public_key(derivation, spend.out_index, self.spend.pub, output_pub))
continue;
crypto::secret_key output_secret{};
crypto::derive_secret_key(derivation, spend.out_index, self.spend.sec, output_secret);
crypto::derive_secret_key(derivation, spend.out_index, get_spend_secret(self, spend.sender), output_secret);
crypto::key_image image{};
crypto::generate_key_image(output_pub, output_secret, image);
@@ -304,6 +320,12 @@ namespace lwsf { namespace internal { namespace backend
}
}
if (!std::uint64_t(source.total_received) && !total_spent)
return false; // used as decoy
if (out.fee <= total_spent)
total_spent -= out.fee;
if (rpc::uint64_string(total_spent) < source.total_received)
{
out.direction = Monero::TransactionInfo::Direction_In;
@@ -314,6 +336,8 @@ namespace lwsf { namespace internal { namespace backend
out.direction = Monero::TransactionInfo::Direction_Out;
out.amount = total_spent - std::uint64_t(source.total_received);
}
return true;
}
void merge_output(const std::shared_ptr<transaction>& out, const rpc::output& source, const crypto::secret_key& view_key)
@@ -326,7 +350,7 @@ namespace lwsf { namespace internal { namespace backend
}
std::vector<std::shared_ptr<transaction>>
merge_response(wallet& self, const rpc::get_address_txs& source, const rpc::get_unspent_outs& unspents)
merge_response(wallet& self, const rpc::get_address_txs& source, const rpc::get_unspent_outs_response& unspents)
{
std::vector<std::shared_ptr<transaction>> out;
@@ -369,10 +393,10 @@ namespace lwsf { namespace internal { namespace backend
for (const auto& tx : source.transactions)
{
auto inserted = updated_txes.try_emplace(tx.tx_hash, nullptr);
auto inserted = updated_txes.try_emplace(tx.hash, nullptr);
if (inserted.second)
{
const auto existing = self.primary.txes.find(tx.tx_hash);
const auto existing = self.primary.txes.find(tx.hash);
if (existing != self.primary.txes.end() && existing->second)
inserted.first->second = std::make_shared<transaction>(*existing->second);
else
@@ -381,7 +405,8 @@ namespace lwsf { namespace internal { namespace backend
if (existing == self.primary.txes.end())
out.push_back(inserted.first->second);
}
update_tx(self.primary, *inserted.first->second, tx);
if (!update_tx(self.primary, *inserted.first->second, tx))
updated_txes.erase(tx.hash);
}
for (const auto& output : unspents.outputs)
@@ -412,6 +437,9 @@ namespace lwsf { namespace internal { namespace backend
std::map<std::uint32_t, sub_account> subaccounts;
subaccounts.swap(self.subaccounts);
// Never remove special {0,0} account
self.subaccounts[0].minor[0] = std::move(subaccounts[0].minor[0]);
for (const auto& subaddr : subaddrs)
{
auto& account = self.subaccounts[subaddr.key];
@@ -421,7 +449,7 @@ namespace lwsf { namespace internal { namespace backend
for (std::uint32_t minor = std::get<0>(minors); minor < std::uint64_t(std::get<1>(minors)) + 1; ++minor)
{
auto elem = account.minor.try_emplace(minor);
if (elem.second)
if (elem.second && (subaddr.key || minor))
elem.first->second = std::move(old.minor[minor]);
}
}
@@ -430,6 +458,8 @@ namespace lwsf { namespace internal { namespace backend
} // anonymous
WIRE_DEFINE_OBJECT(address_book_entry, map_address_book_entry);
WIRE_DEFINE_OBJECT(subaddress, map_subaddress);
WIRE_DEFINE_OBJECT(sub_account, map_sub_account);
WIRE_DEFINE_OBJECT(transfer_out, map_transfer_out);
WIRE_DEFINE_OBJECT(transfer_in, map_transfer_in);
WIRE_DEFINE_OBJECT(transaction, map_transaction);
@@ -566,15 +596,28 @@ namespace lwsf { namespace internal { namespace backend
return {};
}
expect<rpc::get_unspent_outs> outs_response{common_error::kInvalidArgument};
expect<rpc::get_unspent_outs_response> outs_response{common_error::kInvalidArgument};
boost::unique_lock<boost::mutex> lock{sync};
const auto now = std::chrono::steady_clock::now();
if (!mandatory) // double-check
{
const std::chrono::steady_clock::time_point start{
std::chrono::steady_clock::time_point::duration{last_sync.load()}
};
if (now - start < config::refresh_interval)
return {};
}
last_sync = now.time_since_epoch().count();
const rpc::login login{primary.address, primary.view.sec};
lock.unlock();
const auto txs_response = rpc::invoke<rpc::get_address_txs>(client, login);
if (txs_response)
outs_response = rpc::invoke<rpc::get_unspent_outs>(client, login);
{
const rpc::get_unspent_outs_request request{login, rpc::uint64_string(0), 0, true};
outs_response = rpc::invoke<rpc::get_unspent_outs_response>(client, request);
}
if (!txs_response)
return txs_response.error();
@@ -584,7 +627,6 @@ namespace lwsf { namespace internal { namespace backend
lock.lock();
const std::uint64_t orig_scan_height = primary.scan_height;
const auto new_txes = merge_response(*this, *txs_response, *outs_response);
last_sync = std::chrono::steady_clock::now().time_since_epoch().count();
const boost::lock_guard<boost::mutex> lock2{sync_listener};
if (!listener)
@@ -670,6 +712,12 @@ namespace lwsf { namespace internal { namespace backend
return {error::rpc_failure};
}
std::error_code wallet::restore_height(const std::uint64_t height)
{
/* TODO */
throw std::logic_error{"restore_height not implemented"};
}
std::error_code wallet::send_tx(epee::byte_slice tx_bytes)
{
const rpc::submit_raw_tx_request request{std::move(tx_bytes)};
+3 -2
View File
@@ -177,6 +177,7 @@ namespace lwsf { namespace internal { namespace backend
account() = delete;
std::string address; //!> not serialized, recovered on read_bytes
std::string language;
std::vector<address_book_entry> addressbook;
std::map<std::uint32_t, sub_account> subaccounts;
boost::container::flat_map<crypto::hash, std::shared_ptr<transaction>, memory> txes;
@@ -217,11 +218,11 @@ namespace lwsf { namespace internal { namespace backend
//! Attempt
std::error_code login();
std::error_code add_subaccount(std::string label);
//! Refreshes txes information. Strong exception guarantee. Locks contents.
std::error_code refresh(bool mandatory = false);
std::error_code add_subaccount(std::string label);
std::error_code restore_height(const std::uint64_t height);
std::error_code send_tx(epee::byte_slice tx_bytes);
+23
View File
@@ -33,6 +33,29 @@ namespace lwsf
{
switch (value)
{
case error::none:
return "No error";
case error::configuration:
return "Configuration issue";
case error::connect_failure:
return "Connection to server failed";
case error::crypto_failure:
return "Cryptography issue";
case error::invalid_encoding:
return "File had invalid encoding";
case error::invalid_scheme:
return "Invalid URL scheme";
case error::read_failure:
return "Failed to read file";
case error::rpc_failure:
return "RPC failed";
case error::unexpected_userinfo:
return "Unexpected user+pass field in URL";
case error::unsupported_format:
return "Could not unpack file";
case error::write_failure:
return "Failed to write file";
default:
break;
}
+1
View File
@@ -34,6 +34,7 @@ namespace lwsf
enum class error : int
{
none = 0, //!< Must be zero for `expect<..>`
configuration, //!< Bad Configuration
connect_failure, //!< Connection to server failed
crypto_failure, //!< Failure in crypto
invalid_encoding, //!< Invalid percent encoding
+278
View File
@@ -0,0 +1,278 @@
// Copyright (c) 2025, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "lws_frontend.h"
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <ctime>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include "common/command_line.h" // monero/src
#include "common/expect.h" // monero/src
#include "common/password.h" // monero/src
#include "common/util.h" // monero/src
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "error.h"
namespace
{
struct options
{
const command_line::arg_descriptor<std::string> wallet_file;
const command_line::arg_descriptor<std::string> lws_url;
const command_line::arg_descriptor<std::string> network;
const command_line::arg_descriptor<std::string> config_file;
options()
: wallet_file{"wallet-file", "Path to read-and-store wallet. Omit to skip wallet cache storage."}
, lws_url{"lws-url", "<protocol>://address:port of LWS server REST API. Omit to read cached data."}
, network{"network", "<\"main\"|\"stage\"|\"test\"> - Blockchain net type", "main"}
, config_file{"config-file", "Specify any option in a config file; <name>=<value> on separate lines"}
{}
void prepare(boost::program_options::options_description& description) const
{
command_line::add_arg(description, wallet_file);
command_line::add_arg(description, lws_url);
command_line::add_arg(description, config_file);
command_line::add_arg(description, network);
command_line::add_arg(description, command_line::arg_help);
}
Monero::NetworkType get_network(boost::program_options::variables_map const& args) const
{
const std::string net = command_line::get_arg(args, network);
if (net == "main")
return Monero::MAINNET;
else if (net == "stage")
return Monero::STAGENET;
else if (net == "test")
return Monero::TESTNET;
else
throw std::runtime_error{"Bad --network value"};
}
};
struct program
{
std::string wallet_file;
std::string lws_url;
Monero::NetworkType network;
};
void print_help(std::ostream& out)
{
boost::program_options::options_description description{"Options"};
options{}.prepare(description);
out << "Usage: [options]" << std::endl;
out << description;
}
std::optional<program> get_program(int argc, char** argv)
{
namespace po = boost::program_options;
const options opts{};
po::variables_map args{};
{
po::options_description description{"Options"};
opts.prepare(description);
po::store(
po::command_line_parser(argc, argv).options(description).run(), args
);
po::notify(args);
if (!command_line::is_arg_defaulted(args, opts.config_file))
{
std::filesystem::path config_path{command_line::get_arg(args, opts.config_file)};
if (!std::filesystem::exists(config_path))
MONERO_THROW(lwsf::error::configuration, "Config file does not exist");
po::store(
po::parse_config_file<char>(config_path.string().c_str(), description), args
);
po::notify(args);
}
}
if (command_line::get_arg(args, command_line::arg_help))
{
print_help(std::cout);
return std::nullopt;
}
program prog{
command_line::get_arg(args, opts.wallet_file),
command_line::get_arg(args, opts.lws_url),
opts.get_network(args)
};
return prog;
}
void run(program prog)
{
const std::unique_ptr<Monero::WalletManager> wm{
lwsf::WalletManagerFactory::getWalletManager()
};
if (!wm)
throw std::runtime_error{"Unexpected WalletManager nullptr"};
std::unique_ptr<Monero::Wallet> wallet;
{
if (prog.wallet_file.empty() || !wm->walletExists(prog.wallet_file))
{
std::cout << "Input seed words: ";
std::string seed;
std::getline(std::cin, seed, '\n');
std::string pass;
if (!prog.wallet_file.empty())
{
const auto wiped = tools::password_container::prompt(true, "Wallet File Password");
if (!wiped)
throw std::runtime_error{"Failed to get password"};
pass.assign(wiped->password().data(), wiped->password().size());
}
wallet.reset(wm->recoveryWallet(prog.wallet_file, pass, seed, prog.network));
}
else
{
const auto wiped = tools::password_container::prompt(false, "Wallet File Password");
if (!wiped)
throw std::runtime_error{"Failed to get password"};
const std::string pass{wiped->password().data(), wiped->password().size()};
wallet.reset(wm->openWallet(prog.wallet_file, pass, prog.network));
int status = 0;
std::string error;
wallet->statusWithErrorString(status, error);
if (status != Monero::Wallet::Status_Ok)
throw std::runtime_error{"Failed to open wallet: " + error};
}
if (!wallet)
throw std::runtime_error{"Unexpected Wallet nullptr"};
}
if (!prog.lws_url.empty())
{
if (!wallet->init(prog.lws_url, 0, "", "", (prog.lws_url.substr(0, 8) == "https://"), true) || !wallet->refresh())
throw std::runtime_error{"Failed to refresh wallet: " + wallet->errorString()};
}
if (!prog.wallet_file.empty())
{
if (!wallet->store({}))
throw std::runtime_error{"Failed to store wallet file: " + wallet->errorString()};
}
std::cout << "; Primary Address: " << wallet->address() << std::endl;
std::cout << "; Blockchain Height: " << wallet->daemonBlockChainHeight() << std::endl;
std::cout << "; Start Height: " << wallet->getRefreshFromBlockHeight() << std::endl;
std::cout << "; Scan Height: " << wallet->blockChainHeight() << std::endl;
std::cout << "; Accounts: " << wallet->numSubaddressAccounts() << std::endl << std::endl;
Monero::TransactionHistory* history = wallet->history();
if (!history)
throw std::runtime_error{"Unexpected history nullptr"};
history->refresh();
const std::vector<Monero::TransactionInfo*>& info_list = history->getAll();
for (const Monero::TransactionInfo* info : info_list)
{
if (!info)
throw std::runtime_error{"Unexpected info nullptr"};
std::tm expanded{};
const std::time_t timestamp = info->timestamp();
if (!gmtime_r(std::addressof(timestamp), std::addressof(expanded)))
throw std::runtime_error{"gmtime failure"};
char buf[11] = {0};
if (sizeof(buf) - 1 != std::strftime(buf, sizeof(buf), "%Y/%m/%d", std::addressof(expanded)))
throw std::runtime_error{"strftime failed"};
std::cout << buf << " (" << info->hash() << ") " << info->label() << std::endl;
if (info->direction() == Monero::TransactionInfo::Direction_In)
{
std::cout << " Assets:Monero:" << info->subaddrAccount() << " " << cryptonote::print_money(info->amount()) << std::endl;
std::cout << " Income" << std::endl;
}
else
{
std::cout << " Expenses " << cryptonote::print_money(info->amount()) << std::endl;
std::cout << " Expenses:Monero:Fee " << cryptonote::print_money(info->fee()) << std::endl;
std::cout << " Assets:Monero:" << info->subaddrAccount() << std::endl;
}
}
}
}
int main(int argc, char** argv)
{
try
{
std::optional<program> prog;
try
{
prog = get_program(argc, argv);
}
catch (std::exception const& e)
{
std::cerr << e.what() << std::endl << std::endl;
print_help(std::cerr);
return EXIT_FAILURE;
}
if (prog)
run(std::move(*prog));
}
catch (std::exception const& e)
{
std::cerr << e.what() << std::endl;;
return EXIT_FAILURE;
}
catch (...)
{
std::cerr << "Unknown exception" << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
+40 -9
View File
@@ -105,9 +105,9 @@ namespace lwsf { namespace internal { namespace rpc
}
void write_bytes(wire::writer& dest, const empty&)
{ dest.boolean(true); }
void read_bytes(wire::reader& source, const empty&)
{ source.boolean(); }
{ wire::object(dest); }
void read_bytes(wire::reader& source, empty&)
{ wire::object(source); }
void write_bytes(wire::json_writer& dest, const login& self)
{
@@ -129,6 +129,23 @@ namespace lwsf { namespace internal { namespace rpc
wire::object(source, WIRE_OPTIONAL_FIELD(start_height));
}
void read_bytes(wire::json_reader& source, daemon_status& self)
{
wire::object(source,
WIRE_FIELD(outgoing_connections_count),
WIRE_FIELD(incoming_connections_count),
WIRE_FIELD(height),
WIRE_FIELD(target_height)
);
}
void write_bytes(wire::json_writer& dest, const uint64_string source)
{
const auto as_string = dest.to_string(std::uintmax_t(source));
dest.string({as_string.data()});
}
void read_bytes(wire::json_reader& source, uint64_string& dest)
{
dest = uint64_string(wire::integer::cast_unsigned<std::uint64_t>(source.safe_unsigned_integer()));
@@ -170,8 +187,8 @@ namespace lwsf { namespace internal { namespace rpc
WIRE_FIELD(total_received),
WIRE_FIELD(unlock_time),
WIRE_OPTIONAL_FIELD(height),
WIRE_FIELD(tx_hash),
WIRE_FIELD(is_coinbase),
WIRE_FIELD(hash),
WIRE_FIELD(coinbase),
WIRE_FIELD(mempool)
);
@@ -200,10 +217,13 @@ namespace lwsf { namespace internal { namespace rpc
// %Y-%m-%dT%H:%M:%SZ
namespace qi = boost::spirit::qi;
std::tm fields{};
if (!qi::parse(timestamp->begin(), timestamp->end(), qi::ushort_ >> '-' >> qi::ushort_ >> '-' >> qi::ushort_ >> 'T' >> qi::ushort_ >> ':' >> qi::ushort_ >> qi::ushort_ >> 'Z', fields.tm_year, fields.tm_mon, fields.tm_mday, fields.tm_hour, fields.tm_min, fields.tm_sec))
if (!qi::parse(timestamp->begin(), timestamp->end(), qi::ushort_ >> '-' >> qi::ushort_ >> '-' >> qi::ushort_ >> 'T' >> qi::ushort_ >> ':' >> qi::ushort_ >> ':' >> qi::ushort_ >> 'Z', fields.tm_year, fields.tm_mon, fields.tm_mday, fields.tm_hour, fields.tm_min, fields.tm_sec))
WIRE_DLOG_THROW(wire::error::schema::string, "Timestamp string invalid format");
if ((self.timestamp = std::mktime(std::addressof(fields))) == -1)
WIRE_DLOG_THROW(wire::error::schema::string, "Invalid timestamp value");
fields.tm_year -= 1900;
--fields.tm_mon;
fields.tm_isdst = 0;
self.timestamp = timegm(std::addressof(fields));
}
}
@@ -288,7 +308,18 @@ namespace lwsf { namespace internal { namespace rpc
self.rct = std::visit(convert_rct{}, raw_rct.value);
}
void read_bytes(wire::json_reader& source, get_unspent_outs& self)
void write_bytes(wire::json_writer& dest, const get_unspent_outs_request& self)
{
wire::object(dest,
wire::field("address", std::cref(self.creds.address)),
wire::field("view_key", std::cref(self.creds.view_key)),
WIRE_FIELD_COPY(amount),
WIRE_FIELD_COPY(mixin),
WIRE_FIELD_COPY(use_dust)
);
}
void read_bytes(wire::json_reader& source, get_unspent_outs_response& self)
{
using min_output_size = wire::min_element_sizeof<
ringct, crypto::hash, crypto::hash, crypto::public_key, crypto::public_key
+20 -9
View File
@@ -81,7 +81,7 @@ namespace lwsf { namespace internal { namespace rpc
return result.error();
F out{};
error = wire::json::from_bytes(std::move(*result), out);
error = wire::json::from_bytes(epee::to_span(*result), out);
if (error)
return error;
return out;
@@ -134,6 +134,7 @@ namespace lwsf { namespace internal { namespace rpc
enum class uint64_string : std::uint64_t {};
void write_bytes(wire::json_writer&, uint64_string);
void read_bytes(wire::json_reader&, uint64_string&);
struct address_meta
@@ -178,8 +179,8 @@ namespace lwsf { namespace internal { namespace rpc
uint64_string total_received;
std::uint64_t unlock_time;
std::optional<std::uint64_t> height;
crypto::hash tx_hash;
bool is_coinbase;
crypto::hash hash;
bool coinbase;
bool mempool;
transaction()
@@ -190,8 +191,8 @@ namespace lwsf { namespace internal { namespace rpc
total_received(uint64_string(0)),
unlock_time(0),
height(),
tx_hash{},
is_coinbase(false),
hash{},
coinbase(false),
mempool(false)
{}
};
@@ -273,16 +274,26 @@ namespace lwsf { namespace internal { namespace rpc
};
void read_bytes(wire::json_reader&, output&);
struct get_unspent_outs
struct get_unspent_outs_request
{
get_unspent_outs() = delete;
get_unspent_outs_request() = delete;
login creds;
uint64_string amount;
std::uint32_t mixin;
bool use_dust;
};
void write_bytes(wire::json_writer&, const get_unspent_outs_request&);
struct get_unspent_outs_response
{
get_unspent_outs_response() = delete;
static constexpr const char* endpoint() noexcept { return "/get_unspent_outs"; }
std::vector<output> outputs;
std::uint64_t per_byte_fee;
std::uint64_t fee_mask;
};
void read_bytes(wire::json_reader&, get_unspent_outs&);
void read_bytes(wire::json_reader&, get_unspent_outs_response&);
struct import_response
@@ -335,7 +346,7 @@ namespace lwsf { namespace internal { namespace rpc
}}} // lwsf // internal // rpc
WIRE_DECLARE_BLOB_NS(lwsf::internal::rpc::ringct);
WIRE_DECLARE_BLOB(lwsf::internal::rpc::ringct);
namespace std
{
+148
View File
@@ -0,0 +1,148 @@
// Copyright (c) 2025, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "subaddress_account.h"
#include <boost/thread/lock_guard.hpp>
#include <ctime>
#include <utility>
#include "backend.h"
#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src
namespace lwsf { namespace internal
{
namespace
{
struct balance
{
std::uint64_t unlocked;
std::uint64_t total;
balance()
: unlocked(0), total(0)
{}
};
template<typename T, typename value_type = typename T::value_type>
std::vector<value_type> copy(const T& src)
{
std::vector<value_type> out;
out.reserve(src.size());
for (const auto& elem : src)
out.push_back(elem);
return out;
}
void free_rows(std::vector<Monero::SubaddressAccountRow*>& rows) noexcept
{
for (auto row : rows)
delete row;
rows.clear();
}
}
subaddress_account::subaddress_account(std::shared_ptr<backend::wallet> data)
: data_(std::move(data)), rows_()
{
if (!data_)
throw std::invalid_argument{"lwsf::internal::subaddress_account cannot be given nullptr"};
}
subaddress_account::~subaddress_account()
{
free_rows(rows_);
}
void subaddress_account::addRow(const std::string &label)
{
data_->add_subaccount(label);
refresh();
}
void subaddress_account::setLabel(uint32_t accountIndex, const std::string &label)
{
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
data_->primary.subaccounts.at(accountIndex).minor.at(0).label = label;
}
refresh();
}
void subaddress_account::refresh()
{
try
{
free_rows(rows_);
std::unordered_map<std::uint32_t, balance> balances;
const boost::lock_guard<boost::mutex> lock{data_->sync};
const Monero::NetworkType net_type = data_->primary.type;
const std::uint32_t chain_height = data_->blockchain_height;
for (const auto& tx : data_->primary.txes)
{
const bool unlocked = tx.second->is_unlocked(chain_height, net_type);
for (const auto& receive : tx.second->receives)
{
auto& balance = balances[receive.second.recipient.maj_i];
balance.total += receive.second.amount;
if (unlocked)
balance.unlocked += receive.second.amount;
}
for (const auto& spend : tx.second->spends)
{
auto& balance = balances[spend.second.sender.maj_i];
balance.unlocked -= spend.second.amount;
balance.total -= spend.second.amount;
}
}
rows_.reserve(data_->primary.subaccounts.size());
for (const auto& sub : data_->primary.subaccounts)
{
const auto& balance = balances[sub.first];
rows_.push_back(
new Monero::SubaddressAccountRow{
sub.first,
data_->get_spend_address({sub.first, 0}),
sub.second.minor.empty() ? "" : sub.second.minor.begin()->second.label,
cryptonote::print_money(balance.total),
cryptonote::print_money(balance.unlocked)
}
);
}
}
catch (...)
{
free_rows(rows_);
throw;
}
}
}} // lwsf // internal
+106
View File
@@ -0,0 +1,106 @@
// Copyright (c) 2025, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "subaddress_minor.h"
#include <boost/thread/lock_guard.hpp>
#include <ctime>
#include <utility>
#include "backend.h"
#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src
namespace lwsf { namespace internal
{
namespace
{
void free_rows(std::vector<Monero::SubaddressRow*>& rows) noexcept
{
for (auto row : rows)
delete row;
rows.clear();
}
}
subaddress_minor::subaddress_minor(std::shared_ptr<backend::wallet> data)
: data_(std::move(data)), rows_()
{
if (!data_)
throw std::invalid_argument{"lwsf::internal::subaddress_minor cannot be given nullptr"};
}
subaddress_minor::~subaddress_minor()
{
free_rows(rows_);
}
void subaddress_minor::refresh(const std::uint32_t accountIndex)
{
try
{
free_rows(rows_);
const boost::lock_guard<boost::mutex> lock{data_->sync};
const auto account = data_->primary.subaccounts.find(accountIndex);
if (account == data_->primary.subaccounts.end())
return;
rows_.reserve(account->second.minor.size());
for (const auto& minor : account->second.minor)
{
rows_.push_back(
new Monero::SubaddressRow{
minor.first,
data_->get_spend_address({accountIndex, minor.first}),
minor.second.label
}
);
}
}
catch (...)
{
free_rows(rows_);
throw;
}
}
void subaddress_minor::addRow(const std::uint32_t accountIndex, const std::string &label)
{
/* TODO */
}
void subaddress_minor::setLabel(const std::uint32_t accountIndex, const std::uint32_t addressIndex, const std::string &label)
{
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
data_->primary.subaccounts.at(accountIndex).minor.at(addressIndex).label = label;
}
refresh(accountIndex);
}
}} // lwsf // internal
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) 2025, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// 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.
#pragma once
#include <cstdint>
#include <memory>
#include <vector>
#include "wallet/api/wallet2_api.h" // monero/src
namespace lwsf { namespace internal
{
namespace backend { struct wallet; }
class subaddress_minor final : public ::Monero::Subaddress
{
const std::shared_ptr<backend::wallet> data_;
std::vector<Monero::SubaddressRow*> rows_;
public:
explicit subaddress_minor(std::shared_ptr<backend::wallet> data);
subaddress_minor(const subaddress_minor&) = delete;
subaddress_minor(subaddress_minor&&) = delete;
virtual ~subaddress_minor() override;
subaddress_minor& operator=(const subaddress_minor&) = delete;
subaddress_minor& operator=(subaddress_minor&&) = delete;
void refresh(uint32_t accountIndex) override;
std::vector<Monero::SubaddressRow*> getAll() const override { return rows_; }
void addRow(uint32_t accountIndex, const std::string &label) override;
void setLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) override;
};
}} // lwsf // internal
+1 -1
View File
@@ -37,7 +37,7 @@
namespace lwsf { namespace internal
{
namespace backend { class wallet; }
class transaction_history : public Monero::TransactionHistory
class transaction_history final : public Monero::TransactionHistory
{
const std::shared_ptr<backend::wallet> data_;
std::vector<Monero::TransactionInfo*> txes_;
-2
View File
@@ -28,8 +28,6 @@
#pragma once
#include "lws_frontend.h"
#include <memory>
#include <string>
#include <vector>
+320 -44
View File
@@ -42,14 +42,17 @@
#include <string_view>
#include "address_book.h"
#include "backend.h"
#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src
#include "error.h"
#include "hex.h" // monero/contrib/epee/include
#include "hex.h" // monero/contrib/epee/include
#include "lwsf_config.h"
#include "mnemonics/electrum-words.h" // monero/src
#include "net/net_parse_helpers.h" // monero/contrib/epee/include
#include "net/parse.h" // monero/src
#include "net/socks.h" // monero/src
#include "net/socks_connect.h" // monero/src
#include "subaddress_account.h"
#include "subaddress_minor.h"
#include "transaction_history.h"
#include "wire.h"
#include "wire/msgpack.h"
@@ -59,6 +62,9 @@ namespace lwsf { namespace internal
namespace
{
//! The stored wallet file always has this at beginning
static constexpr std::string_view file_magic{"lwsf-wallet-1.0"};
struct null_connector
{
boost::unique_future<boost::asio::ip::tcp::socket>
@@ -150,7 +156,7 @@ namespace lwsf { namespace internal
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())
if (std::numeric_limits<std::size_t>::max() - upayload.size() < tag_size())
throw std::runtime_error{"Exceeded max size_t after authentication tag"};
encrypted_file out{
@@ -201,6 +207,7 @@ namespace lwsf { namespace internal
throw std::runtime_error{"Failed to initialize libsodium"};
const auto key = get_key(password);
static_assert(key_size() == key.size());
epee::byte_stream buffer;
buffer.put_n(0, epayload.size());
@@ -208,13 +215,14 @@ namespace lwsf { namespace internal
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)
nonce.data(), key.data()) != 0)
{
throw std::runtime_error{std::string{cipher_name()} + " decryption failed"};
}
@@ -241,18 +249,27 @@ namespace lwsf { namespace internal
epee::byte_slice try_load(const std::string& filename)
{
std::ifstream file{filename, std::ios::binary | std::ios::ate};
std::ifstream file{filename, std::ios::binary};
if (!file.is_open())
return nullptr;
{
std::string magic;
magic.resize(file_magic.size());
file.read(magic.data(), magic.size());
if (!file.good() || magic != file_magic)
return nullptr;
}
file.seekg(0, std::ios::end);
const auto size = file.tellg();
if (size < 0)
if (size < 0 || std::size_t(size) < file_magic.size())
return nullptr;
epee::byte_stream buffer;
buffer.put_n(0, std::size_t(size));
buffer.put_n(0, std::size_t(size) - file_magic.size());
file.seekg(0);
file.seekg(file_magic.size());
file.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
if (file.good())
@@ -271,7 +288,10 @@ namespace lwsf { namespace internal
bool rc = false;
if (0 <= write(fd, contents.data(), contents.size()) && 0 <= fsync(fd))
{
const int dir = open(directory.c_str(), O_RDONLY);
const char* cwd = directory.c_str();
if (strcmp(cwd, "") == 0)
cwd = ".";
const int dir = open(cwd, O_RDONLY);
if (0 <= dir)
{
if (0 <= fsync(dir))
@@ -303,6 +323,23 @@ namespace lwsf { namespace internal
);
return boost::chrono::nanoseconds{source.count()};
}
void load_wallet(backend::account& self, const crypto::secret_key& from, const Monero::NetworkType nettype, const bool generated_locally)
{
cryptonote::account_base base{};
base.generate(from, true);
const auto& keys = base.get_keys();
self.view.sec = keys.m_view_secret_key;
self.view.pub = keys.m_account_address.m_view_public_key;
self.spend.sec = keys.m_spend_secret_key;
self.spend.pub = keys.m_account_address.m_spend_public_key;
self.generated_locally = generated_locally;
self.type = nettype;
}
} // anonymous
bool wallet::set_error(const std::error_code status) const
@@ -368,8 +405,54 @@ namespace lwsf { namespace internal
}
}
wallet::wallet(create_tag, Monero::NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds)
bool wallet::is_wallet_file(const std::string& path)
{
return !try_load(path).empty();
}
bool wallet::verify_password(std::string path, const std::string& password)
{
// We don't use a separate keys file, remove the suffix is present
static constexpr const std::string_view keys_suffix{".keys"};
if (keys_suffix.size() <= path.size() && std::string_view{path.data() + path.size() - keys_suffix.size()} == keys_suffix)
path.erase(path.size() - keys_suffix.size());
epee::byte_slice file = try_load(path);
if (file.empty())
return false;
encrypted_file contents{};
if (!wire::msgpack::from_bytes(std::move(file), contents))
return false;
return !contents.get_payload(password).empty();
}
std::vector<std::string> wallet::find(const std::string& path)
{
std::vector<std::string> out;
std::filesystem::path work_dir(path);
if(!std::filesystem::is_directory(path))
return out;
std::filesystem::recursive_directory_iterator end_itr;
for (std::filesystem::recursive_directory_iterator itr(path); itr != end_itr; ++itr)
{
if (std::filesystem::is_regular_file(itr->status()))
{
std::string filename = itr->path().filename().string();
if (!try_load(filename).empty())
out.push_back(std::move(filename));
}
}
return out;
}
wallet::wallet(create, Monero::NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds)
: data_(std::make_shared<backend::wallet>()),
addressbook_(),
history_(),
subaddresses_(),
subaddress_minor_(),
filename_(std::move(filename)),
password_(std::move(password)),
exception_error_(),
@@ -391,26 +474,15 @@ namespace lwsf { namespace internal
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(std::string{});
load_wallet(data_->primary, recovery, nettype, true);
}
wallet::wallet(open_tag, Monero::NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds, std::shared_ptr<backend::wallet> data)
: data_(std::move(data)),
wallet::wallet(open, Monero::NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds)
: data_(std::make_shared<backend::wallet>()),
addressbook_(),
history_(),
subaddresses_(),
subaddress_minor_(),
filename_(std::move(filename)),
password_(std::move(password)),
exception_error_(),
@@ -463,8 +535,82 @@ namespace lwsf { namespace internal
}
}
wallet::wallet(from_mnemonic, const Monero::NetworkType nettype, std::string filename, std::string password, const std::uint64_t kdf_rounds, const std::string& mnemonic, const std::string& seed_offset)
: data_(std::make_shared<backend::wallet>()),
addressbook_(),
history_(),
subaddresses_(),
subaddress_minor_(),
filename_(std::move(filename)),
password_(std::move(password)),
exception_error_(),
status_(),
thread_(),
iterations_(kdf_rounds),
mixin_(config::mixin_default),
refresh_interval_(config::refresh_interval),
refresh_notify_(),
error_sync_(),
refresh_sync_(),
thread_state_(state::stop),
mandatory_refresh_(false)
{
if (sodium_init() < 0)
throw std::runtime_error{"Failed to initialize libsodium"};
crypto::secret_key recovery;
std::string language;
if (!crypto::ElectrumWords::words_to_bytes(mnemonic, recovery, language))
{
exception_error_ = "Electrum-style word list failed verification";
return;
}
if (!seed_offset.empty())
recovery = cryptonote::decrypt_key(recovery, seed_offset);
load_wallet(data_->primary, recovery, nettype, false);
}
wallet::~wallet() { stop_refresh_loop(); }
std::string wallet::seed(const std::string& seed_offset) const
{
std::string language;
crypto::secret_key key;
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
language = data_->primary.language;
key = data_->primary.spend.sec;
}
if (language.empty())
language = "English";
if (!seed_offset.empty())
key = cryptonote::encrypt_key(key, seed_offset);
epee::wipeable_string electrum_words;
if (!crypto::ElectrumWords::bytes_to_words(key, electrum_words, language))
{
set_critical(std::runtime_error{"Failed to crate seed from key for " + language});
return {};
}
return std::string{electrum_words.data(), electrum_words.size()};
}
std::string wallet::getSeedLanguage() const
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
return data_->primary.language;
}
void wallet::setSeedLanguage(const std::string &arg)
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
data_->primary.language = arg;
}
int wallet::status() const
{
int out = 0;
@@ -507,6 +653,11 @@ namespace lwsf { namespace internal
return true;
}
std::string wallet::address(const std::uint32_t accountIndex, const std::uint32_t addressIndex) const
{
return data_->get_spend_address({accountIndex, addressIndex});
}
Monero::NetworkType wallet::nettype() const { return data_->primary.type; }
std::string wallet::secretViewKey() const
@@ -551,6 +702,7 @@ namespace lwsf { namespace internal
epee::byte_stream buffer;
buffer.reserve(payload_size + 2048);
buffer.write(file_magic.data(), file_magic.size());
if (!(status = wire::msgpack::to_bytes(buffer, contents)))
{
@@ -610,7 +762,7 @@ namespace lwsf { namespace internal
stop_refresh_loop();
data_->client.disconnect();
if (!setProxy(proxy_address))
if (!proxy_address.empty() && !setProxy(proxy_address))
return false;
epee::net_utils::http::url_content url{};
@@ -622,9 +774,12 @@ namespace lwsf { namespace internal
if (url.schema == "https")
use_ssl = true;
epee::net_utils::http::login login{daemon_username, daemon_password};
const epee::net_utils::ssl_options_t options{
use_ssl ? epee::net_utils::ssl_support_t::e_ssl_support_disabled : epee::net_utils::ssl_support_t::e_ssl_support_enabled
boost::optional<epee::net_utils::http::login> login;
if (!daemon_username.empty() || !daemon_password.empty())
login.emplace(daemon_username, daemon_password);
epee::net_utils::ssl_options_t options{
use_ssl ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled
};
data_->client.set_server(std::move(url.host), std::to_string(url.port), std::move(login), std::move(options));
@@ -703,17 +858,75 @@ namespace lwsf { namespace internal
return true;
}
uint64_t wallet::balance(const uint32_t accountIndex) const
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
std::uint64_t balance = 0;
for (const auto& tx : data_->primary.txes)
{
for (const auto& receive : tx.second->receives)
{
if (receive.second.recipient.maj_i == accountIndex)
balance += receive.second.amount;
}
for (const auto& spend : tx.second->spends)
{
if (spend.second.sender.maj_i == accountIndex)
balance -= spend.second.amount;
}
}
return balance;
}
uint64_t wallet::unlockedBalance(const uint32_t accountIndex) const
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
const Monero::NetworkType net_type = data_->primary.type;
const std::uint32_t chain_height = data_->blockchain_height;
std::uint64_t balance = 0;
for (const auto& tx : data_->primary.txes)
{
const bool unlocked = tx.second->is_unlocked(chain_height, net_type);
for (const auto& receive : tx.second->receives)
{
if (unlocked && receive.second.recipient.maj_i == accountIndex)
balance += receive.second.amount;
}
for (const auto& spend : tx.second->spends)
{
if (spend.second.sender.maj_i == accountIndex)
balance -= spend.second.amount;
}
}
return balance;
}
uint64_t wallet::blockChainHeight() const
{
const boost::unique_lock<boost::mutex> lock{data_->sync};
return data_->blockchain_height;
return data_->primary.scan_height;
}
uint64_t wallet::daemonBlockChainHeight() const
{ return blockChainHeight(); }
{
const boost::unique_lock<boost::mutex> lock{data_->sync};
return data_->blockchain_height;
}
uint64_t wallet::daemonBlockChainTargetHeight() const
{ return blockChainHeight(); }
{ return daemonBlockChainHeight(); }
bool wallet::synchronized() const
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
return data_->blockchain_height == data_->primary.scan_height;
}
void wallet::startRefresh()
{
@@ -732,7 +945,7 @@ namespace lwsf { namespace internal
bool wallet::refresh()
{
return set_error(data_->refresh(true));
return set_error(data_->refresh(false));
}
void wallet::refreshAsync()
@@ -767,6 +980,63 @@ namespace lwsf { namespace internal
return refresh_interval_.count();
}
void wallet::addSubaddressAccount(const std::string& label)
{
data_->add_subaccount(label);
}
std::size_t wallet::numSubaddressAccounts() const
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
return data_->primary.subaccounts.size();
}
std::size_t wallet::numSubaddresses(const std::uint32_t accountIndex) const
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
const auto account = data_->primary.subaccounts.find(accountIndex);
if (account != data_->primary.subaccounts.end())
return account->second.minor.size();
set_critical(std::runtime_error{"numSubaddresses failed, " + std::to_string(accountIndex) + " does not exist"});
return 0;
}
std::string wallet::getSubaddressLabel(const std::uint32_t accountIndex, const std::uint32_t addressIndex) const
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
const auto major = data_->primary.subaccounts.find(accountIndex);
if (major != data_->primary.subaccounts.end())
{
const auto minor = major->second.minor.find(addressIndex);
if (minor != major->second.minor.end())
return minor->second.label;
}
set_critical(std::runtime_error{"getSubaddressLabel failed, " + std::to_string(accountIndex) + "," + std::to_string(addressIndex) + " does not exist"});
return {};
}
void wallet::setSubaddressLabel(const std::uint32_t accountIndex, const std::uint32_t addressIndex, const std::string &label)
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
auto major = data_->primary.subaccounts.find(accountIndex);
if (major != data_->primary.subaccounts.end())
{
auto minor = major->second.minor.find(addressIndex);
if (minor != major->second.minor.end())
{
minor->second.label = label;
return;
}
}
set_critical(std::runtime_error{"setSubaddressLabel failed, " + std::to_string(accountIndex) + "," + std::to_string(addressIndex) + " does not exist"});
}
void wallet::disposeTransaction(Monero::PendingTransaction * t)
{
delete t;
}
uint64_t wallet::estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations,
Monero::PendingTransaction::Priority priority) const
{
@@ -805,6 +1075,13 @@ namespace lwsf { namespace internal
return addressbook_.get();
}
Monero::Subaddress* wallet::subaddress()
{
if (!subaddress_minor_)
subaddress_minor_ = std::make_unique<subaddress_minor>(data_);
return subaddress_minor_.get();
}
Monero::TransactionHistory* wallet::history()
{
if (!history_)
@@ -825,7 +1102,8 @@ namespace lwsf { namespace internal
const boost::lock_guard<boost::mutex> lock{data_->sync_listener};
data_->listener = listener;
}
listener->onSetWallet(this);
if (listener)
listener->onSetWallet(this);
}
std::uint32_t wallet::defaultMixin() const
@@ -842,15 +1120,17 @@ namespace lwsf { namespace internal
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
data_->primary.attributes.try_emplace(key).first->second = val;
return true;
}
std::string wallet::getCacheAttribute(const std::string &key) const
{
const boost::lock_guard<boost::mutex> lock{data_->sync};
const auto iter = data_->primary.attributes.find(key);
if (iter == data_->primary.attributes.end())
return {};
return iter->second;
const auto attribute = data_->primary.attributes.find(key);
if (attribute != data_->primary.attributes.end())
return attribute->second;
set_critical(std::runtime_error{"attribute " + key + " does not exist"});
return {};
}
bool wallet::setUserNote(const std::string &txid, const std::string &note)
@@ -921,8 +1201,4 @@ namespace lwsf { namespace internal
return out;
}
bool wallet::checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations)
{
}
}} // lwsf // internal
+138 -127
View File
@@ -46,9 +46,7 @@ namespace internal
{
namespace backend { struct wallet; }
//! \TODO Mark final when completely implemented
class wallet : public Monero::Wallet
class wallet final : public Monero::Wallet
{
enum class state : std::uint8_t { stop = 0, paused, skip_once, run };
@@ -56,8 +54,10 @@ namespace internal
std::unique_ptr<Monero::AddressBook> addressbook_;
std::unique_ptr<Monero::TransactionHistory> history_;
std::unique_ptr<Monero::SubaddressAccount> subaddresses_;
std::unique_ptr<Monero::Subaddress> subaddress_minor_;
const std::string filename_;
std::string password_;
std::string language_;
mutable std::string exception_error_;
mutable std::error_code status_;
boost::thread thread_;
@@ -77,11 +77,19 @@ namespace internal
void refresh_loop();
public:
struct create_tag{};
struct open_tag{};
struct create{};
struct from_keys{};
struct from_mnemonic{};
struct open{};
wallet(create_tag, Monero::NetworkType nettype, std::string filename, std::string password, std::uint64_t kdf_rounds);
wallet(open_tag, Monero::NetworkType nettpe, std::string filename, std::string password, std::uint64_t kdf_rounds, std::shared_ptr<backend::wallet> data);
static bool is_wallet_file(const std::string& path);
static bool verify_password(std::string path, const std::string& password);
static std::vector<std::string> find(const std::string& path);
wallet(create, Monero::NetworkType nettype, std::string filename, std::string password, std::uint64_t kdf_rounds);
wallet(open, Monero::NetworkType nettpe, std::string filename, std::string password, std::uint64_t kdf_rounds);
wallet(from_mnemonic, Monero::NetworkType nettype, std::string path, std::string password, std::uint64_t kdf_rounds, const std::string& mnemonic, const std::string& seed_offset);
wallet(from_keys, Monero::NetworkType nettype, std::string path, std::string password, std::uint64_t kdf_rounds, const std::string& mnemonic, const std::string& seed_offset);
wallet(const wallet&) = delete;
wallet(wallet&&) = delete;
@@ -89,10 +97,10 @@ namespace internal
wallet& operator=(const wallet&) = delete;
wallet& operator=(wallet&&) = delete;
virtual std::string seed(const std::string& seed_offset = "") const = 0;
virtual std::string seed(const std::string& seed_offset = "") const override;
virtual std::string getSeedLanguage() const = 0;
virtual void setSeedLanguage(const std::string &arg) = 0;
virtual std::string getSeedLanguage() const override;
virtual void setSeedLanguage(const std::string &arg) override;
virtual int status() const override;
virtual std::string errorString() const override;
@@ -104,12 +112,12 @@ namespace internal
virtual bool setDevicePin(const std::string &) override { return false; };
virtual bool setDevicePassphrase(const std::string &) override { return false; };
virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const override;
virtual std::string path() const = 0;
virtual std::string path() const override { return filename_; }
virtual Monero::NetworkType nettype() const override;
//! returns current hard fork info
virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const override { /* TODO */ };
//! check if hard fork rules should be used
virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
virtual bool useForkRules(uint8_t version, int64_t early_blocks) const override { /* TODO */ return false; };
/*!
* \brief integratedAddress - returns integrated address for current wallet address and given payment_id.
* if passed "payment_id" param is an empty string or not-valid payment id string
@@ -119,7 +127,7 @@ namespace internal
* generated
* \return - 106 characters string representing integrated address
*/
virtual std::string integratedAddress(const std::string &payment_id) const = 0;
virtual std::string integratedAddress(const std::string &payment_id) const override { /* TODO */ return {}; }
/*!
* \brief secretViewKey - returns secret view key
@@ -173,7 +181,7 @@ namespace internal
* \brief keysFilename - returns keys filename. usually this formed as "wallet_filename".keys
* \return
*/
virtual std::string keysFilename() const = 0;
virtual std::string keysFilename() const override { return filename_; }
/*!
* \brief init - initializes wallet with daemon connection params.
* if daemon_address is local address, "trusted daemon" will be set to true forcibly
@@ -194,7 +202,7 @@ namespace internal
* \param language
* \return - true if created successfully
*/
virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const override { /* TODO */ return false; }
/*!
* \brief setRefreshFromBlockHeight - start refresh from block height on recover
@@ -214,14 +222,14 @@ namespace internal
*
* \param recoveringFromSeed - true/false
*/
virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0;
virtual void setRecoveringFromSeed(bool recoveringFromSeed) override { /* TODO */ };
/*!
* \brief setRecoveringFromDevice - set state to recovering from device
*
* \param recoveringFromDevice - true/false
*/
virtual void setRecoveringFromDevice(bool recoveringFromDevice) = 0;
virtual void setRecoveringFromDevice(bool recoveringFromDevice) override { /* TODO */ }
/*!
* \brief setSubaddressLookahead - set size of subaddress lookahead
@@ -229,7 +237,7 @@ namespace internal
* \param major - size fot the major index
* \param minor - size fot the minor index
*/
virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) = 0;
virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) override { /* TODO */ }
/**
* @brief connectToDaemon - connects to the daemon. TODO: check if it can be removed
@@ -245,32 +253,20 @@ namespace internal
virtual void setTrustedDaemon(bool) override {}
virtual bool trustedDaemon() const override { return true; }
virtual bool setProxy(const std::string &address) override;
virtual uint64_t balance(uint32_t accountIndex = 0) const = 0;
uint64_t balanceAll() const {
uint64_t result = 0;
for (uint32_t i = 0; i < numSubaddressAccounts(); ++i)
result += balance(i);
return result;
}
virtual uint64_t unlockedBalance(uint32_t accountIndex = 0) const = 0;
uint64_t unlockedBalanceAll() const {
uint64_t result = 0;
for (uint32_t i = 0; i < numSubaddressAccounts(); ++i)
result += unlockedBalance(i);
return result;
}
virtual uint64_t balance(uint32_t accountIndex = 0) const override;
virtual uint64_t unlockedBalance(uint32_t accountIndex = 0) const override;
/**
* @brief watchOnly - checks if wallet is watch only
* @return - true if watch only
*/
virtual bool watchOnly() const = 0;
virtual bool watchOnly() const override { /* TODO */ return false; }
/**
* @brief isDeterministic - checks if wallet keys are deterministic
* @return - true if deterministic
*/
virtual bool isDeterministic() const = 0;
virtual bool isDeterministic() const override { return true; }
/**
* @brief blockChainHeight - returns current blockchain height
@@ -282,14 +278,14 @@ namespace internal
* @brief approximateBlockChainHeight - returns approximate blockchain height calculated from date/time
* @return
*/
virtual uint64_t approximateBlockChainHeight() const = 0;
virtual uint64_t approximateBlockChainHeight() const override { /* TODO */ return 0; }
/**
* @brief estimateBlockChainHeight - returns estimate blockchain height. More accurate than approximateBlockChainHeight,
* uses daemon height and falls back to calculation from date/time
* @return
**/
virtual uint64_t estimateBlockChainHeight() const = 0;
virtual uint64_t estimateBlockChainHeight() const override { /* TODO */ return 0; }
/**
* @brief daemonBlockChainHeight - returns daemon blockchain height
* @return 0 - in case error communicating with the daemon.
@@ -308,36 +304,7 @@ namespace internal
* @brief synchronized - checks if wallet was ever synchronized
* @return
*/
virtual bool synchronized() const = 0;
static std::string displayAmount(uint64_t amount);
static uint64_t amountFromString(const std::string &amount);
static uint64_t amountFromDouble(double amount);
static std::string genPaymentId();
static bool paymentIdValid(const std::string &paiment_id);
static bool addressValid(const std::string &str, Monero::NetworkType nettype);
static bool addressValid(const std::string &str, bool testnet) // deprecated
{
return addressValid(str, testnet ? Monero::TESTNET : Monero::MAINNET);
}
static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, Monero::NetworkType nettype, std::string &error);
static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error) // deprecated
{
return keyValid(secret_key_string, address_string, isViewKey, testnet ? Monero::TESTNET : Monero::MAINNET, error);
}
static std::string paymentIdFromAddress(const std::string &str, Monero::NetworkType nettype);
static std::string paymentIdFromAddress(const std::string &str, bool testnet) // deprecated
{
return paymentIdFromAddress(str, testnet ? Monero::TESTNET : Monero::MAINNET);
}
static uint64_t maximumAllowedAmount();
// Easylogger wrapper
static void init(const char *argv0, const char *default_log_base_name) { init(argv0, default_log_base_name, "", true); }
static void init(const char *argv0, const char *default_log_base_name, const std::string &log_path, bool console);
static void debug(const std::string &category, const std::string &str);
static void info(const std::string &category, const std::string &str);
static void warning(const std::string &category, const std::string &str);
static void error(const std::string &category, const std::string &str);
virtual bool synchronized() const override;
/**
* @brief StartRefresh - Start/resume refresh thread (refresh every 10 seconds)
@@ -363,12 +330,12 @@ namespace internal
* @brief rescanBlockchain - rescans the wallet, updating transactions from daemon
* @return - true if refreshed successfully;
*/
virtual bool rescanBlockchain() = 0;
virtual bool rescanBlockchain() override { /* TODO */ return false; }
/**
* @brief rescanBlockchainAsync - rescans wallet asynchronously, starting from genesys
*/
virtual void rescanBlockchainAsync() = 0;
virtual void rescanBlockchainAsync() override { /* TODO */ }
/**
* @brief setAutoRefreshInterval - setup interval for automatic refresh.
@@ -386,84 +353,93 @@ namespace internal
* @brief addSubaddressAccount - appends a new subaddress account at the end of the last major index of existing subaddress accounts
* @param label - the label for the new account (which is the as the label of the primary address (accountIndex,0))
*/
virtual void addSubaddressAccount(const std::string& label) = 0;
virtual void addSubaddressAccount(const std::string& label) override;
/**
* @brief numSubaddressAccounts - returns the number of existing subaddress accounts
*/
virtual size_t numSubaddressAccounts() const = 0;
virtual size_t numSubaddressAccounts() const override;
/**
* @brief numSubaddresses - returns the number of existing subaddresses associated with the specified subaddress account
* @param accountIndex - the major index specifying the subaddress account
*/
virtual size_t numSubaddresses(uint32_t accountIndex) const = 0;
virtual size_t numSubaddresses(uint32_t accountIndex) const override;
/**
* @brief addSubaddress - appends a new subaddress at the end of the last minor index of the specified subaddress account
* @param accountIndex - the major index specifying the subaddress account
* @param label - the label for the new subaddress
*/
virtual void addSubaddress(uint32_t accountIndex, const std::string& label) = 0;
virtual void addSubaddress(uint32_t accountIndex, const std::string& label) override { /* TODO */ }
/**
* @brief getSubaddressLabel - gets the label of the specified subaddress
* @param accountIndex - the major index specifying the subaddress account
* @param addressIndex - the minor index specifying the subaddress
*/
virtual std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const = 0;
virtual std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const override;
/**
* @brief setSubaddressLabel - sets the label of the specified subaddress
* @param accountIndex - the major index specifying the subaddress account
* @param addressIndex - the minor index specifying the subaddress
* @param label - the new label for the specified subaddress
*/
virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0;
virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) override;
/**
* @brief multisig - returns current state of multisig wallet creation process
* @return MultisigState struct
*/
virtual Monero::MultisigState multisig() const = 0;
virtual Monero::MultisigState multisig() const override { /* TODO */ return {}; }
/**
* @brief getMultisigInfo
* @return serialized and signed multisig info string
*/
virtual std::string getMultisigInfo() const = 0;
virtual std::string getMultisigInfo() const override { /* TODO */ return {}; }
/**
* @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets
* @param info - vector of multisig infos from other participants obtained with getMulitisInfo call
* @param threshold - number of required signers to make valid transaction. Must be <= number of participants
* @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info
*/
virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0;
virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override { /* TODO */ return {}; }
/**
* @brief exchange_multisig_keys - provides additional key exchange round for arbitrary multisig schemes (like N-1/N, M/N)
* @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call
* @param force_update_use_with_caution - force multisig account to update even if not all signers contribute round messages
* @return new info string if more rounds required or an empty string if wallet creation is done
*/
virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution) = 0;
virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution) override { /* TODO */ return {}; }
/**
* @brief getMultisigKeyExchangeBooster - obtain partial information for the key exchange round after the in-progress round,
* to speed up another signer's key exchange process
* @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call
* @param threshold - number of required signers to make valid transaction. Must be <= number of participants.
* @param num_signers - total number of multisig participants.
* @return new info string if more rounds required or exception if no more rounds (i.e. no rounds to boost)
*/
virtual std::string getMultisigKeyExchangeBooster(const std::vector<std::string> &info, const uint32_t threshold, const uint32_t num_signers) override { /* TODO */ return {}; }
/**
* @brief exportMultisigImages - exports transfers' key images
* @param images - output paramter for hex encoded array of images
* @return true if success
*/
virtual bool exportMultisigImages(std::string& images) = 0;
virtual bool exportMultisigImages(std::string& images) override { /* TODO */ return false; }
/**
* @brief importMultisigImages - imports other participants' multisig images
* @param images - array of hex encoded arrays of images obtained with exportMultisigImages
* @return number of imported images
*/
virtual size_t importMultisigImages(const std::vector<std::string>& images) = 0;
virtual size_t importMultisigImages(const std::vector<std::string>& images) override { /* TODO */ return false; }
/**
* @brief hasMultisigPartialKeyImages - checks if wallet needs to import multisig key images from other participants
* @return true if there are partial key images
*/
virtual bool hasMultisigPartialKeyImages() const = 0;
virtual bool hasMultisigPartialKeyImages() const override { /* TODO */ return false; }
/**
* @brief restoreMultisigTransaction creates PendingTransaction from signData
* @param signData encrypted unsigned transaction. Obtained with PendingTransaction::multisigSignData
* @return PendingTransaction
*/
virtual Monero::PendingTransaction* restoreMultisigTransaction(const std::string& signData) = 0;
virtual Monero::PendingTransaction* restoreMultisigTransaction(const std::string& signData) override { /* TODO */ return nullptr; }
/*!
* \brief createTransactionMultDest creates transaction with multiple destinations. if dst_addr is an integrated address, payment_id is ignored
@@ -482,7 +458,7 @@ namespace internal
Monero::optional<std::vector<uint64_t>> amount, uint32_t mixin_count,
Monero::PendingTransaction::Priority = Monero::PendingTransaction::Priority_Low,
uint32_t subaddr_account = 0,
std::set<uint32_t> subaddr_indices = {}) = 0;
std::set<uint32_t> subaddr_indices = {}) override { /* TODO */ return nullptr; }
/*!
* \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored
@@ -501,7 +477,7 @@ namespace internal
std::optional<uint64_t> amount, uint32_t mixin_count,
Monero::PendingTransaction::Priority = Monero::PendingTransaction::Priority_Low,
uint32_t subaddr_account = 0,
std::set<uint32_t> subaddr_indices = {}) = 0;
std::set<uint32_t> subaddr_indices = {}) override { /* TODO */ return nullptr; }
/*!
* \brief createSweepUnmixableTransaction creates transaction with unmixable outputs.
@@ -509,28 +485,27 @@ namespace internal
* after object returned
*/
virtual Monero::PendingTransaction* createSweepUnmixableTransaction() = 0;
virtual Monero::PendingTransaction* createSweepUnmixableTransaction() override { /* TODO */ return nullptr; }
/*!
* \brief loadUnsignedTx - creates transaction from unsigned tx file
* \return - UnsignedTransaction object. caller is responsible to check UnsignedTransaction::status()
* after object returned
*/
virtual Monero::UnsignedTransaction* loadUnsignedTx(const std::string &unsigned_filename) = 0;
virtual Monero::UnsignedTransaction* loadUnsignedTx(const std::string &unsigned_filename) override { /* TODO */ return nullptr; }
/*!
* \brief submitTransaction - submits transaction in signed tx file
* \return - true on success
*/
virtual bool submitTransaction(const std::string &fileName) = 0;
virtual bool submitTransaction(const std::string &fileName) override { /* TODO */ return false; }
/*!
* \brief disposeTransaction - destroys transaction object
* \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned;
*/
virtual void disposeTransaction(Monero::PendingTransaction * t) override final
{}
virtual void disposeTransaction(Monero::PendingTransaction * t) override;
/*!
* \brief Estimates transaction fee.
@@ -576,9 +551,45 @@ namespace internal
*/
virtual bool scanTransactions(const std::vector<std::string> &txids) override;
/*!
* \brief setupBackgroundSync - setup background sync mode with just a view key
* \param background_sync_type - the mode the wallet background syncs in
* \param wallet_password
* \param background_cache_password - custom password to encrypt background cache, only needed for custom password background sync type
* \return - true on success
*/
virtual bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const Monero::optional<std::string> &background_cache_password) override { return true; }
/*!
* \brief getBackgroundSyncType - get mode the wallet background syncs in
* \return - the type, or off if type is unknown
*/
virtual BackgroundSyncType getBackgroundSyncType() const override { return Monero::Wallet::BackgroundSync_ReusePassword; }
/**
* @brief startBackgroundSync - sync the chain in the background with just view key
*/
virtual bool startBackgroundSync() override { return true; }
/**
* @brief stopBackgroundSync - bring back spend key and process background synced txs
* \param wallet_password
*/
virtual bool stopBackgroundSync(const std::string &wallet_password) override { return false; }
/**
* @brief isBackgroundSyncing - returns true if the wallet is background syncing
*/
virtual bool isBackgroundSyncing() const override { return true; }
/**
* @brief isBackgroundWallet - returns true if the wallet is a background wallet
*/
virtual bool isBackgroundWallet() const override { return true; }
virtual Monero::TransactionHistory* history() override;
virtual Monero::AddressBook* addressBook() override;
virtual Monero::Subaddress* subaddress() = 0;
virtual Monero::Subaddress* subaddress() override;
virtual Monero::SubaddressAccount* subaddressAccount() override;
virtual void setListener(Monero::WalletListener*) override;
/*!
@@ -604,7 +615,7 @@ namespace internal
* \param key - the key
* \return the attached string, or empty string if there is none
*/
virtual std::string getCacheAttribute(const std::string &key) const = 0;
virtual std::string getCacheAttribute(const std::string &key) const override;
/*!
* \brief setUserNote - attach an arbitrary string note to a txid
* \param txid - the transaction id to attach the note to
@@ -619,24 +630,24 @@ namespace internal
*/
virtual std::string getUserNote(const std::string &txid) const override;
virtual std::string getTxKey(const std::string &txid) const override;
virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0;
virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const = 0;
virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0;
virtual std::string getSpendProof(const std::string &txid, const std::string &message) const = 0;
virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const = 0;
virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) override { /* TODO */ return false; }
virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const override { /* TODO */ return {}; }
virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) override { /* TODO */ return false; }
virtual std::string getSpendProof(const std::string &txid, const std::string &message) const override { /* TODO */ return {}; }
virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const override { /* TODO */ return false; }
/*!
* \brief getReserveProof - Generates a proof that proves the reserve of unspent funds
* Parameters `account_index` and `amount` are ignored when `all` is true
*/
virtual std::string getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const = 0;
virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const = 0;
virtual std::string getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const override { /* TODO */ return {}; }
virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const override { /* TODO */ return false; }
/*
* \brief signMessage - sign a message with the spend private key
* \param message - the message to sign (arbitrary byte data)
* \return the signature
*/
virtual std::string signMessage(const std::string &message, const std::string &address = "") = 0;
virtual std::string signMessage(const std::string &message, const std::string &address = "") override { /* TODO */ return {}; }
/*!
* \brief verifySignedMessage - verify a signature matches a given message
* \param message - the message (arbitrary byte data)
@@ -644,14 +655,14 @@ namespace internal
* \param signature - the signature
* \return true if the signature verified, false otherwise
*/
virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0;
virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const override { /* TODO */ return false; }
/*!
* \brief signMultisigParticipant signs given message with the multisig public signer key
* \param message message to sign
* \return signature in case of success. Sets status to Error and return empty string in case of error
*/
virtual std::string signMultisigParticipant(const std::string &message) const = 0;
virtual std::string signMultisigParticipant(const std::string &message) const override { /* TODO */ return {}; }
/*!
* \brief verifyMessageWithPublicKey verifies that message was signed with the given public key
* \param message message
@@ -659,78 +670,78 @@ namespace internal
* \param signature signature of the message
* \return true if the signature is correct. false and sets error state in case of error
*/
virtual bool verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const = 0;
virtual bool verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const override { /* TODO */ return false; }
virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0;
virtual std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) const = 0;
virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) override { /* TODO */ return false; }
virtual std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) const override { /* TODO */ return {}; }
virtual std::string getDefaultDataDir() const = 0;
virtual std::string getDefaultDataDir() const override { /* TODO */ return {}; }
/*
* \brief rescanSpent - Rescan spent outputs - Can only be used with trusted daemon
* \return true on success
*/
virtual bool rescanSpent() = 0;
virtual bool rescanSpent() override { return false; }
/*
* \brief setOffline - toggle set offline on/off
* \param offline - true/false
*/
virtual void setOffline(bool offline) = 0;
virtual bool isOffline() const = 0;
virtual void setOffline(bool offline) override { /* TODO */ }
virtual bool isOffline() const override { /* TODO */ return false; }
//! blackballs a set of outputs
virtual bool blackballOutputs(const std::vector<std::string> &outputs, bool add) = 0;
virtual bool blackballOutputs(const std::vector<std::string> &outputs, bool add) override { return false; }
//! blackballs an output
virtual bool blackballOutput(const std::string &amount, const std::string &offset) = 0;
virtual bool blackballOutput(const std::string &amount, const std::string &offset) override { return false; }
//! unblackballs an output
virtual bool unblackballOutput(const std::string &amount, const std::string &offset) = 0;
virtual bool unblackballOutput(const std::string &amount, const std::string &offset) override { return false; }
//! gets the ring used for a key image, if any
virtual bool getRing(const std::string &key_image, std::vector<uint64_t> &ring) const = 0;
virtual bool getRing(const std::string &key_image, std::vector<uint64_t> &ring) const override { /* TODO */ return false; }
//! gets the rings used for a txid, if any
virtual bool getRings(const std::string &txid, std::vector<std::pair<std::string, std::vector<uint64_t>>> &rings) const = 0;
virtual bool getRings(const std::string &txid, std::vector<std::pair<std::string, std::vector<uint64_t>>> &rings) const override { /* TODO */ return false; }
//! sets the ring used for a key image
virtual bool setRing(const std::string &key_image, const std::vector<uint64_t> &ring, bool relative) = 0;
virtual bool setRing(const std::string &key_image, const std::vector<uint64_t> &ring, bool relative) override { /* TODO */ return false; }
//! sets whether pre-fork outs are to be segregated
virtual void segregatePreForkOutputs(bool segregate) = 0;
virtual void segregatePreForkOutputs(bool segregate) override { /* TODO */ }
//! sets the height where segregation should occur
virtual void segregationHeight(uint64_t height) = 0;
virtual void segregationHeight(uint64_t height) override { /* TODO */ }
//! secondary key reuse mitigation
virtual void keyReuseMitigation2(bool mitigation) = 0;
virtual void keyReuseMitigation2(bool mitigation) override { /* TODO */ }
//! locks/unlocks the keys file; returns true on success
virtual bool lockKeysFile() = 0;
virtual bool unlockKeysFile() = 0;
virtual bool lockKeysFile() override { return false; }
virtual bool unlockKeysFile() override { return true; }
//! returns true if the keys file is locked
virtual bool isKeysFileLocked() = 0;
virtual bool isKeysFileLocked() override { return false; }
/*!
* \brief Queries backing device for wallet keys
* \return Device they are on
*/
virtual Device getDeviceType() const = 0;
virtual Device getDeviceType() const override { /* TODO */ return {}; }
//! cold-device protocol key image sync
virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) = 0;
virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) override { /* TODO */ return 0; }
//! shows address on device display
virtual void deviceShowAddress(uint32_t accountIndex, uint32_t addressIndex, const std::string &paymentId) = 0;
virtual void deviceShowAddress(uint32_t accountIndex, uint32_t addressIndex, const std::string &paymentId) override { /* TODO */ }
//! attempt to reconnect to hardware device
virtual bool reconnectDevice() = 0;
virtual bool reconnectDevice() override { /* TODO */ return false; }
//! get bytes received
virtual uint64_t getBytesReceived() = 0;
virtual uint64_t getBytesReceived() override { /* TODO */ return 0; }
//! get bytes sent
virtual uint64_t getBytesSent() = 0;
virtual uint64_t getBytesSent() override { /* TODO */ return 0; }
};
}} // lwsf // internal
+35 -20
View File
@@ -96,9 +96,7 @@ namespace lwsf
*/
Monero::Wallet* createWallet(const std::string &path, const std::string &password, const std::string &language, Monero::NetworkType nettype, uint64_t kdf_rounds) override
{
return new wallet{
wallet::create_tag{}, nettype, path, password, kdf_rounds
};
return new wallet{wallet::create{}, nettype, path, password, kdf_rounds};
}
/*!
@@ -113,15 +111,11 @@ namespace lwsf
Monero::Wallet* openWallet(const std::string &path, const std::string &password, Monero::NetworkType nettype, uint64_t kdf_rounds, Monero::WalletListener* listener) override
{
auto data = std::make_shared<backend::wallet>();
data->listener = std::move(listener);
auto out = std::make_unique<wallet>(
wallet::open_tag{}, nettype, path, password, kdf_rounds, data
wallet::open{}, nettype, path, password, kdf_rounds
);
// wallet file should be fully loaded in construction of `wallet`.
if (data->listener)
data->listener->onSetWallet(out.get());
if (listener)
out->setListener(listener);
return out.release();
}
@@ -140,7 +134,11 @@ namespace lwsf
Monero::NetworkType nettype = Monero::MAINNET, uint64_t restoreHeight = 0, uint64_t kdf_rounds = 1,
const std::string &seed_offset = {}) override final
{
//! \TODO Complete
auto out = std::make_unique<wallet>(
wallet::from_mnemonic{}, nettype, path, password, kdf_rounds, mnemonic, seed_offset
);
out->setRefreshFromBlockHeight(restoreHeight);
return out.release();
}
/*!
@@ -154,6 +152,7 @@ namespace lwsf
*/
Monero::Wallet* recoveryWallet(const std::string &path, const std::string &mnemonic, Monero::NetworkType nettype, uint64_t restoreHeight = 0) override
{
return recoveryWallet(path, "", mnemonic, nettype, restoreHeight);
}
/*!
@@ -236,9 +235,10 @@ namespace lwsf
*/
bool closeWallet(Monero::Wallet* const wallet, const bool store) override
{
const std::unique_ptr<Monero::Wallet> destroy{wallet};
if (wallet && store)
wallet->store(wallet->filename());
if (wallet && store && !wallet->store({}))
return false;
delete wallet;
return true;
}
/*
@@ -252,7 +252,7 @@ namespace lwsf
*/
bool walletExists(const std::string &path) override
{
return std::filesystem::exists(std::filesystem::path{path});
return lwsf::internal::wallet::is_wallet_file(path);
}
/*!
@@ -267,9 +267,9 @@ namespace lwsf
* This function will fail when the wallet keys file is opened because the wallet program locks the keys file.
* In this case, Wallet::unlockKeysFile() and Wallet::lockKeysFile() need to be called before and after the call to this function, respectively.
*/
bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds) const override
bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool, uint64_t) const override
{
//! \TODO Complete
lwsf::internal::wallet::verify_password(keys_file_name, password);
}
/*!
@@ -294,7 +294,7 @@ namespace lwsf
*/
std::vector<std::string> findWallets(const std::string &path) override
{
//! \TODO Complete
return lwsf::internal::wallet::find(path);
}
std::string errorString() const override { return error_; }
@@ -302,13 +302,28 @@ namespace lwsf
//! set the daemon address (hostname and port)
void setDaemonAddress(const std::string &address) override
{
//! \TODO Complete
epee::net_utils::http::url_content url{};
if (!epee::net_utils::parse_url(address, url))
throw std::runtime_error{"Invalid LWS URL: " + address};
if (!url.m_uri_content.m_path.empty())
throw std::runtime_error{"LWS URL contains path (unsupported)"};
bool use_ssl = false;
if (url.schema == "https")
use_ssl = true;
const epee::net_utils::ssl_options_t options{
use_ssl ? epee::net_utils::ssl_support_t::e_ssl_support_disabled : epee::net_utils::ssl_support_t::e_ssl_support_enabled
};
client_.set_server(std::move(url.host), std::to_string(url.port), boost::none, std::move(options));
}
//! returns whether the daemon can be reached, and its version number
bool connected(uint32_t *version = NULL) override
{
version = 0;
if (version)
*version = 0;
return client_.is_connected();
}
+5 -5
View File
@@ -26,12 +26,12 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(monero-lws-wire_sources basic_value.cpp error.cpp read.cpp write.cpp)
set(monero-lws-wire_headers basic_value.h error.h field.h filters.h fwd.h json.h read.h traits.h vector.h write.h)
set(lwsf-wire_sources basic_value.cpp error.cpp read.cpp write.cpp)
set(lwsf-wire_headers basic_value.h error.h field.h filters.h fwd.h json.h read.h traits.h vector.h write.h)
add_library(monero-lws-wire ${monero-lws-wire_sources} ${monero-lws-wire_headers})
target_include_directories(monero-lws-wire PUBLIC "${LMDB_INCLUDE}")
target_link_libraries(monero-lws-wire PRIVATE monero::libraries)
add_library(lwsf-wire ${lwsf-wire_sources} ${lwsf-wire_headers})
target_include_directories(lwsf-wire PUBLIC "${LMDB_INCLUDE}")
target_link_libraries(lwsf-wire PRIVATE monero::libraries)
add_subdirectory(json)
add_subdirectory(msgpack)
+10 -10
View File
@@ -39,7 +39,7 @@ namespace crypto
template<typename R>
void read_bytes(R& source, crypto::secret_key& self)
{
source.binary(epee::as_mut_byte_span(unwrap(unwrap(self))));
source.binary(epee::as_mut_byte_span(unwrap(unwrap(self))), /* exact */ true);
}
template<typename W>
@@ -51,13 +51,13 @@ namespace crypto
namespace wire
{
WIRE_DECLARE_BLOB(crypto::ec_scalar);
WIRE_DECLARE_BLOB(crypto::hash);
WIRE_DECLARE_BLOB(crypto::hash8);
WIRE_DECLARE_BLOB(crypto::key_derivation);
WIRE_DECLARE_BLOB(crypto::key_image);
WIRE_DECLARE_BLOB(crypto::public_key);
WIRE_DECLARE_BLOB(crypto::signature);
WIRE_DECLARE_BLOB(crypto::view_tag);
WIRE_DECLARE_BLOB(rct::key);
WIRE_DECLARE_BLOB_NS(crypto::ec_scalar);
WIRE_DECLARE_BLOB_NS(crypto::hash);
WIRE_DECLARE_BLOB_NS(crypto::hash8);
WIRE_DECLARE_BLOB_NS(crypto::key_derivation);
WIRE_DECLARE_BLOB_NS(crypto::key_image);
WIRE_DECLARE_BLOB_NS(crypto::public_key);
WIRE_DECLARE_BLOB_NS(crypto::signature);
WIRE_DECLARE_BLOB_NS(crypto::view_tag);
WIRE_DECLARE_BLOB_NS(rct::key);
}
+1 -1
View File
@@ -49,6 +49,6 @@ namespace wire
}
void write_bytes(writer& dest, const basic_value& source)
{
boost::apply_visitor([&dest] (const auto& val) { wire_write::bytes(dest, val); }, source.value);
std::visit([&dest] (const auto& val) { wire_write::bytes(dest, val); }, source.value);
}
}
+4 -4
View File
@@ -26,8 +26,8 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(monero-lws_wire-json_sources error.cpp read.cpp write.cpp)
set(monero-lws_wire-json_headers base.h error.h fwd.h read.h write.h)
set(lwsf_wire-json_sources error.cpp read.cpp write.cpp)
set(lwsf_wire-json_headers base.h error.h fwd.h read.h write.h)
add_library(monero-lws-wire-json ${monero-lws_wire-json_sources} ${monero-lws-wire-json_headers})
target_link_libraries(monero-lws-wire-json monero::libraries monero-lws-wire)
add_library(lwsf-wire-json ${lwsf_wire-json_sources} ${lwsf-wire-json_headers})
target_link_libraries(lwsf-wire-json monero::libraries lwsf-wire)
+2 -2
View File
@@ -43,9 +43,9 @@ namespace wire
using output_type = json_slice_writer;
template<typename T>
static std::error_code from_bytes(std::string&& source, T& dest)
static std::error_code from_bytes(const epee::span<const char> source, T& dest)
{
return wire_read::from_bytes<input_type>(std::move(source), dest);
return wire_read::from_bytes<input_type>(source, dest);
}
template<typename T, typename U>
+19 -4
View File
@@ -25,7 +25,7 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "serialization/wire/json/read.h"
#include "wire/json/read.h"
#include <algorithm>
#include <limits>
@@ -34,9 +34,9 @@
#include "byte_stream.h"
#include "hex.h"
#include "serialization/wire/basic_value.h"
#include "serialization/wire/error.h"
#include "serialization/wire/json/error.h"
#include "wire/basic_value.h"
#include "wire/error.h"
#include "wire/json/error.h"
namespace
{
@@ -345,6 +345,21 @@ namespace wire
}
return std::string{std::move(str_buffer_)};
}
std::size_t json_reader::string(epee::span<char> dest, bool exact)
{
{
rapidjson_sax json_string{str_buffer_, error::schema::string};
read_next_value(json_string);
}
if (!exact && str_buffer_.size() < dest.size())
dest = {dest.data(), str_buffer_.size()};
if (str_buffer_.size() != dest.size())
WIRE_DLOG_THROW(error::schema::string, "expected size " << dest.size() << " but got " << str_buffer_.size());
return dest.size();
}
epee::byte_slice json_reader::binary()
{
+32 -22
View File
@@ -28,18 +28,14 @@
#pragma once
#include <boost/utility/string_ref.hpp>
#include <cstddef>
#include <cstdint>
#include <rapidjson/reader.h>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "wire/field.h"
#include "byte_slice.h" // monero/contrib/epee/include
#include "wire/fwd.h"
#include "wire/json/base.h"
#include "wire/read.h"
#include "wire/traits.h"
#include "span.h" // monero/contrib/epee/include
namespace wire
{
@@ -48,7 +44,7 @@ namespace wire
{
struct rapidjson_sax;
std::string source_;
std::string str_buffer_;
rapidjson::Reader reader_;
void read_next_value(rapidjson_sax& handler);
@@ -58,14 +54,21 @@ namespace wire
//! Skips next value. \throw wire::exception if invalid JSON syntax.
void skip_value();
//! \throw wire::exception if next token not `[`.
std::size_t do_start_array(std::size_t) override final;
//! \throw wire::exception if next token not `{`.
std::size_t do_start_object() override final;
public:
explicit json_reader(std::string&& source);
//! `source` must "outlive" `json_reader`.
explicit json_reader(epee::span<const char> source);
//! \throw wire::exception if JSON parsing is incomplete.
void check_complete() const override final;
//! \throw wire::exception if array, object, or end of stream.
basic_value basic();
basic_value basic() override final;
//! \throw wire::exception if next token not a boolean.
bool boolean() override final;
@@ -76,7 +79,7 @@ namespace wire
//! \throw wire::exception if next token not an unsigned integer.
std::uintmax_t unsigned_integer() override final;
//! \throw wire::exception if next token is not an integer encoded as string OR unsigned integer
//! \throw wire::exception if next token is not an integer encoded as string
std::uintmax_t safe_unsigned_integer();
//! \throw wire::exception if next token not a valid real number
@@ -85,23 +88,30 @@ namespace wire
//! \throw wire::exception if next token not a string
std::string string() override final;
/*! Copy upcoming string directly into `dest`.
\throw wire::exception if next value not string
\throw wire::exception if next string exceeds `dest.size())`
\throw wire::exception if `exact == true` and next string size is not
`dest.size()`
\return Number of bytes read into `dest`. */
std::size_t string(epee::span<char> dest, bool exact) override final;
//! \throw wire::exception if next token cannot be read as hex
epee::byte_slice binary() override final;
//! \throw wire::exception if next token cannot be read as hex into `dest`.
void binary(epee::span<std::uint8_t> dest) override final;
//! \throw wire::exception if next token not `[`.
std::size_t start_array(std::size_t) override final;
/* Copy upcoming binary, encoded as a hex string, into `dest`.
\throw wire::exception if next value is not a string
\throw wire::exception if next string is not valid hex.
\throw wire::exception if `dest.size() * 2` is less than next string length
\throw wire::exception if `exact == true` and next string length is
not exactly `dest.size() * 2`
\return Number of bytes read into `dest` (not number of bytes read
from wire). */
std::size_t binary(epee::span<std::uint8_t> dest, bool exact) override final;
//! Skips whitespace to next token. \return True if next token is eof or ']'.
bool is_array_end(std::size_t count) override final;
//! \throw wire::exception if next token not `{`.
std::size_t start_object() override final;
/*! \throw wire::exception if next token not key or `}`.
\param[out] index of key match within `map`.
\return True if another value to read. */
+4 -4
View File
@@ -26,8 +26,8 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(monero-lws_wire-msgpack_sources error.cpp read.cpp write.cpp)
set(monero-lws_wire-msgpack_headers base.h error.h fwd.h read.h write.h)
set(lwsf_wire-msgpack_sources error.cpp read.cpp write.cpp)
set(lwsf_wire-msgpack_headers base.h error.h fwd.h read.h write.h)
add_library(monero-lws-wire-msgpack ${monero-lws_wire-msgpack_sources} ${monero-lws-wire-msgpack_headers})
target_link_libraries(monero-lws-wire-msgpack monero::libraries monero-lws-wire)
add_library(lwsf-wire-msgpack ${lwsf_wire-msgpack_sources} ${lwsf-wire-msgpack_headers})
target_link_libraries(lwsf-wire-msgpack monero::libraries lwsf-wire)
+77 -6
View File
@@ -36,6 +36,7 @@
#include <stdexcept>
#include <type_traits>
#include "wire/basic_value.h"
#include "wire/error.h"
#include "wire/msgpack/error.h"
@@ -331,7 +332,7 @@ namespace wire
return read_integer<std::intmax_t>(remaining_, next);
}
std::uintmax_t msgpack_reader::do_unsigned_integer(const msgpack::tag next)
std::uintmax_t msgpack_reader:: do_unsigned_integer(const msgpack::tag next)
{
return read_integer<std::uintmax_t>(remaining_, next);
}
@@ -366,6 +367,62 @@ namespace wire
WIRE_DLOG_THROW_(error::msgpack::incomplete);
}
basic_value msgpack_reader::basic()
{
update_tags_remaining();
const auto next = get_tag();
if (std::uint8_t(next) <= msgpack::ftag_unsigned::max())
return {std::uintmax_t(next)};
if (msgpack::ftag_signed::matches(next))
return {do_integer(next)};
if (msgpack::ftag_string::matches(next))
{
const auto bytes = read_string(remaining_, next);
return {std::string{reinterpret_cast<const char*>(bytes.data()), bytes.size()}};
}
switch (next)
{
case msgpack::tag::nil:
return {nullptr};
case msgpack::tag::True:
return {true};
case msgpack::tag::False:
return {false};
case msgpack::tag::int8:
case msgpack::tag::int16:
case msgpack::tag::int32:
case msgpack::tag::int64:
return {do_integer(next)};
case msgpack::tag::uint8:
case msgpack::tag::uint16:
case msgpack::tag::uint32:
case msgpack::tag::uint64:
return {do_unsigned_integer(next)};
/*
case msgpack::tag::binary8:
case msgpack::tag::binary16:
case msgpack::tag::binary32:
return {epee::byte_slice{{read_binary(remaining_, next)}}}; */
case msgpack::tag::string8:
case msgpack::tag::string16:
case msgpack::tag::string32:
{
const auto bytes = read_string(remaining_, next);
return {std::string{reinterpret_cast<const char*>(bytes.data()), bytes.size()}};
}
default:
break;
}
WIRE_DLOG_THROW(error::schema::number, "expected a boolean, integer, float or string");
}
bool msgpack_reader::boolean()
{
update_tags_remaining();
@@ -413,6 +470,19 @@ namespace wire
return std::string{reinterpret_cast<const char*>(bytes.data()), bytes.size()};
}
std::size_t msgpack_reader::string(epee::span<char> dest, bool exact)
{
update_tags_remaining();
const epee::span<const std::uint8_t> bytes = read_string(remaining_, get_tag());
if (!exact && bytes.size() < dest.size())
dest = {dest.data(), bytes.size()};
if (dest.size() != bytes.size())
WIRE_DLOG_THROW(error::schema::string, "of size " << dest.size() << " but got " << bytes.size());
std::memcpy(dest.data(), bytes.data(), dest.size());
return dest.size();
}
epee::byte_slice msgpack_reader::binary()
{
update_tags_remaining();
@@ -421,16 +491,19 @@ namespace wire
return source_.get_slice(begin, begin + bytes.size());
}
void msgpack_reader::binary(epee::span<std::uint8_t> dest)
std::size_t msgpack_reader::binary(epee::span<std::uint8_t> dest, const bool exact)
{
update_tags_remaining();
const epee::span<const std::uint8_t> bytes = read_binary(remaining_, get_tag());
if (!exact && bytes.size() < dest.size())
dest = {dest.data(), bytes.size()};
if (dest.size() != bytes.size())
WIRE_DLOG_THROW(error::schema::fixed_binary, "of size " << dest.size() << " but got " << bytes.size());
std::memcpy(dest.data(), bytes.data(), dest.size());
return dest.size();
}
std::size_t msgpack_reader::start_array(const std::size_t min_element_size)
std::size_t msgpack_reader::do_start_array(const std::size_t min_element_size)
{
const std::size_t upcoming =
read_count<msgpack::ftag_array, msgpack::array_types>(error::schema::array);
@@ -440,7 +513,6 @@ namespace wire
WIRE_DLOG_THROW(error::schema::array, upcoming << " array elements of at least " << min_element_size << " bytes each exceeds " << remaining_.size() << " remaining bytes");
tags_remaining_ += upcoming;
increment_depth();
return upcoming;
}
@@ -452,7 +524,7 @@ namespace wire
return true;
}
std::size_t msgpack_reader::start_object()
std::size_t msgpack_reader::do_start_object()
{
const std::size_t upcoming =
read_count<msgpack::ftag_object, msgpack::object_types>(error::schema::object);
@@ -461,7 +533,6 @@ namespace wire
if (limits<std::size_t>::max() - tags_remaining_ < upcoming * 2)
WIRE_DLOG_THROW_(error::msgpack::max_tree_size);
tags_remaining_ += upcoming * 2;
increment_depth();
return upcoming;
}
+19 -10
View File
@@ -75,6 +75,13 @@ namespace wire
template<typename T, typename U>
std::size_t read_count(error::schema);
//! \throw wire::exception if next token not `[`.
std::size_t do_start_array(std::size_t min_element_size) override final;
//! \throw wire::exception if next token not `{`.
std::size_t do_start_object() override final;
public:
explicit msgpack_reader(epee::byte_slice&& source)
: reader(nullptr), source_(std::move(source)), tags_remaining_(1)
@@ -85,6 +92,9 @@ namespace wire
//! \throw wire::exception if JSON parsing is incomplete.
void check_complete() const override final;
//! \throw wire::exception if array, object, or end of stream.
basic_value basic() override final;
//! \throw wire::exception if next token not a boolean.
bool boolean() override final;
@@ -114,23 +124,22 @@ namespace wire
//! \throw wire::exception if next token not a string
std::string string() override final;
/*! Copy upcoming string directly into `dest`.
\throw wire::exception if next value not string
\throw wire::exception if next string exceeds `dest.size())`
\throw wire::exception if `exact == true` and next string is not `dest.size()`
\return Number of bytes read into `dest`. */
std::size_t string(epee::span<char> dest, bool exact) override final;
//! \throw wire::exception if next token cannot be read as hex
epee::byte_slice binary() override final;
//! \throw wire::exception if next token cannot be read as hex into `dest`.
void binary(epee::span<std::uint8_t> dest) override final;
//! \throw wire::exception if next token not `[`.
std::size_t start_array(std::size_t min_element_size) override final;
std::size_t binary(epee::span<std::uint8_t> dest, bool exact) override final;
//! \return true when `count == 0`.
bool is_array_end(const std::size_t count) override final;
//! \throw wire::exception if next token not `{`.
std::size_t start_object() override final;
/*! \throw wire::exception if next token not key or `}`.
\param[out] index of key match within `map`.
\return True if another value to read. */
+17 -5
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2020, The Monero Project
// Copyright (c) 2021, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
@@ -42,8 +42,21 @@ void wire::reader::decrement_depth()
--depth_;
}
[[noreturn]] void wire::integer::throw_exception(std::intmax_t source, std::intmax_t min, std::intmax_t max)
std::size_t wire::reader::start_array(std::size_t min_element_size)
{
increment_depth();
return do_start_array(min_element_size);
}
std::size_t wire::reader::start_object()
{
increment_depth();
return do_start_object();
}
[[noreturn]] void wire::integer::throw_exception(const std::intmax_t source, const std::intmax_t min, const std::intmax_t max)
{
static_assert(0 <= std::numeric_limits<std::intmax_t>::max(), "expected 0 <= intmax_t::max");
static_assert(
std::numeric_limits<std::intmax_t>::max() <= std::numeric_limits<std::uintmax_t>::max(),
"expected intmax_t::max <= uintmax_t::max"
@@ -53,9 +66,9 @@ void wire::reader::decrement_depth()
else
throw_exception(std::uintmax_t(source), std::uintmax_t(max));
}
[[noreturn]] void wire::integer::throw_exception(std::uintmax_t source, std::uintmax_t max)
[[noreturn]] void wire::integer::throw_exception(const std::uintmax_t source, const std::uintmax_t max)
{
WIRE_DLOG_THROW(error::schema::smaller_integer, source << " given when " << max << "is maximum permitted");
WIRE_DLOG_THROW(error::schema::smaller_integer, source << " given when " << max << " is maximum permitted");
}
[[noreturn]] void wire_read::throw_exception(const wire::error::schema code, const char* display, epee::span<char const* const> names)
@@ -72,4 +85,3 @@ void wire::reader::decrement_depth()
WIRE_DLOG_THROW(code, display << (name ? name : ""));
}
+98 -56
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2020-2023, The Monero Project
// Copyright (c) 2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
@@ -30,23 +30,52 @@
#include <cstdint>
#include <limits>
#include <string>
#include <tuple>
#include <type_traits>
#include "byte_slice.h" // monero/contrib/epee/include
#include "common/expect.h" // monero/src
#include "span.h" // monero/contrib/epee/include
#include "byte_slice.h" // monero/contrib/epee/include
#include "wire/error.h"
#include "wire/field.h"
#include "wire/fwd.h"
#include "wire/traits.h"
#include "span.h" // monero/contrib/epee/include
/*
Custom types (e.g. `type` in namespace `ns`) can define an input function by:
* `namespace wire { template<> struct is_blob<ns::type> : std::true_type {}; }`
* `namespace wire { void read_bytes(writer&, ns::type&); }`
* `namespace ns { void read_bytes(wire::writer&, type&); }`
See `traits.h` for `is_blob` requirements. `read_bytes` function can also
specify derived type for faster output (i.e.
`namespace ns { void read_bytes(wire::epee_reader&, type&); }`). Using the
derived type allows the compiler to de-virtualize and allows for custom
functions not defined by base interface. Using base interface allows for
multiple formats with minimal instruction count. */
namespace wire
{
//! Interface for converting "wire" (byte) formats to C/C++ objects without a DOM.
class reader
{
{
std::size_t depth_; //!< Tracks number of recursive objects and arrays
//! \throw wire::exception if max depth is reached
void increment_depth();
//! \throw std::logic_error if already `depth() == 0`.
void decrement_depth();
/*! \param min_element_size of each array element in any format - if known.
Derived types with explicit element count should verify available
space, and throw a `wire::exception` on issues.
\throw wire::exception if next value not array
\throw wire::exception if not enough bytes for all array elements
(with epee/msgpack which has specified number of elements).
\return Number of values to read before calling `is_array_end()`. */
virtual std::size_t do_start_array(std::size_t min_element_size) = 0;
//! \throw wire::exception if not object begin. \return State to be given to `key(...)` function.
virtual std::size_t do_start_object() = 0;
protected:
epee::span<const std::uint8_t> remaining_; //!< Derived class tracks unprocessed bytes here
@@ -54,22 +83,16 @@ namespace wire
: depth_(0), remaining_(remaining)
{}
reader(const reader&) = default;
reader(reader&&) = default;
reader& operator=(const reader&) = default;
reader& operator=(reader&&) = default;
//! \throw wire::exception if max depth is reached
void increment_depth();
//! \throw std::logic_error if already `depth() == 0`.
void decrement_depth();
reader(const reader&) = delete;
reader(reader&&) = delete;
reader& operator=(const reader&) = delete;
reader& operator=(reader&&) = delete;
public:
struct key_map
{
const char* name;
unsigned id; //<! For integer key formats;
const char* name;
unsigned id; //<! For integer key formats;
};
//! \return Maximum read depth for both objects and arrays before erroring
@@ -90,10 +113,13 @@ namespace wire
//! \throw wire::exception if parsing is incomplete.
virtual void check_complete() const = 0;
//! \throw wire::exception if array, object, or end of stream.
virtual basic_value basic() = 0;
//! \throw wire::exception if next value not a boolean.
virtual bool boolean() = 0;
//! \throw wire::exception if next value not an integer.
//! \throw wire::expception if next value not an integer.
virtual std::intmax_t integer() = 0;
//! \throw wire::exception if next value not an unsigned integer.
@@ -105,30 +131,40 @@ namespace wire
//! throw wire::exception if next value not string
virtual std::string string() = 0;
/*! Copy upcoming string directly into `dest`.
\throw wire::exception if next value not string
\throw wire::exception if next string exceeds `dest.size())`
\throw wire::exception if `exact == true` and next string is not `dest.size()`
\return Number of bytes read into `dest`. */
virtual std::size_t string(epee::span<char> dest, bool exact) = 0;
// ! \throw wire::exception if next value cannot be read as binary
virtual epee::byte_slice binary() = 0;
//! \throw wire::exception if next value cannot be read as binary into `dest`.
virtual void binary(epee::span<std::uint8_t> dest) = 0;
/*! Copy upcoming binary directly into `dest`.
\throw wire::exception if next value not binary
\throw wire::exception if next binary exceeds `dest.size()`
\throw wire::exception if `exact == true` and next binary is not `dest.size()`.
\return Number of bytes read into `dest`. */
virtual std::size_t binary(epee::span<std::uint8_t> dest, const bool exact) = 0;
/* \param min_element_size of each array element in any format - if known.
/*! \param min_element_size of each array element in any format - if known.
Derived types with explicit element count should verify available
space, and throw a `wire::exception` on issues.
\throw wire::exception if next value not array
\throw wire::exception if not enough bytes for all array elements
(with epee/msgpack which has specified number of elements).
\return Number of values to read before calling `is_array_end()` */
virtual std::size_t start_array(std::size_t min_element_size) = 0;
\return Number of values to read before calling `is_array_end()`. */
std::size_t start_array(std::size_t min_element_size);
//! \return True if there is another element to read.
virtual bool is_array_end(std::size_t count) = 0;
//! \throw wire::exception if array end delimiter not present.
void end_array() { decrement_depth(); }
//! \throw wire::exception if not object begin. \return State to be given to `key(...)` function.
virtual std::size_t start_object() = 0;
std::size_t start_object();
/*! Read a key of an object field and match against a known list of keys.
Skips or throws exceptions on unknown fields depending on implementation
@@ -168,7 +204,7 @@ namespace wire
template<typename R, typename T>
inline std::enable_if_t<is_blob<T>::value> read_bytes(R& source, T& dest)
{ source.binary(epee::as_mut_byte_span(dest)); }
{ source.binary(epee::as_mut_byte_span(dest), /*exact=*/ true); }
//! Use `read_bytes(...)` method if available for `T`.
template<typename R, typename T>
@@ -193,7 +229,7 @@ namespace wire
"source must be signed integer type"
);
if (source < limit::min() || limit::max() < source)
throw_exception(source, limit::min(), limit::max());
throw_exception(source, limit::min(), limit::max());
return static_cast<T>(source);
}
@@ -213,7 +249,7 @@ namespace wire
throw_exception(source, limit::max());
return static_cast<T>(source);
}
}
} // integer
//! read all current and future signed integer types
template<typename R, typename T>
@@ -234,11 +270,12 @@ namespace wire
namespace wire_read
{
/*! Don't add a function called `read_bytes` to this namespace, it will prevent
ADL lookup. ADL lookup delays the function searching until the template
is used instead of when its defined. This allows the unqualified calls to
`read_bytes` in this namespace to "find" user functions that are declared
after these functions (the technique behind `boost::serialization`). */
/*! Don't add a function called `read_bytes` to this namespace, it will
prevent 2-phase lookup. 2-phase lookup delays the function searching until
the template is used instead of when its defined. This allows the
unqualified calls to `read_bytes` in this namespace to "find" user
functions that are declared after these functions (the technique behind
`boost::serialization`). */
[[noreturn]] void throw_exception(wire::error::schema code, const char* display, epee::span<char const* const> name_list);
@@ -248,10 +285,13 @@ namespace wire_read
read_bytes(source, dest); // ADL (searches every associated namespace)
}
//! \return `T` converted from `source` or error.
//! Use `source` to store information at `dest`
template<typename R, typename T, typename U>
inline std::error_code from_bytes(T&& source, U& dest)
{
if (wire::is_optional_root<U>::value && source.empty())
return {};
try
{
R in{std::forward<T>(source)};
@@ -262,6 +302,7 @@ namespace wire_read
{
return e.code();
}
return {};
}
@@ -273,17 +314,18 @@ namespace wire_read
}
// Insert to sorted containers
template<typename R, typename T, typename V = std::pair<typename T::key_type, typename T::mapped_type>>
template<typename R, typename T, typename V = typename T::value_type>
inline auto array_insert(R& source, T& dest) -> decltype(dest.emplace_hint(dest.end(), std::declval<V>()), bool(true))
{
V val{};
using value_type = typename wire::remove_const_key<V>::type;
value_type val{};
wire_read::bytes(source, val);
dest.emplace_hint(dest.end(), std::move(val));
return true;
}
// Insert into unsorted containers
template<typename R, typename T, typename V = typename T::value_type>
template<typename R, typename T>
inline auto array_insert(R& source, T& dest) -> decltype(dest.emplace_back(), dest.back(), bool(true))
{
// more efficient to process the object in-place in many cases
@@ -307,10 +349,6 @@ namespace wire_read
if (max_element_count < count)
throw_exception(wire::error::schema::array_max_element, "", nullptr);
// also checked by derived formats when count is known
if (min_element_size && (source.remaining().size() / min_element_size) < count)
throw_exception(wire::error::schema::array_min_size, "", nullptr);
dest.clear();
wire::reserve(dest, count);
@@ -360,13 +398,13 @@ namespace wire_read
}
template<typename R, typename T, unsigned I>
inline void unpack_field(std::size_t, R& source, wire::field_<T, true, I>& dest)
inline void unpack_field(R& source, wire::field_<T, true, I>& dest)
{
bytes(source, dest.get_value());
}
template<typename R, typename T, unsigned I>
inline void unpack_field(std::size_t, R& source, wire::field_<T, false, I>& dest)
inline void unpack_field(R& source, wire::field_<T, false, I>& dest)
{
if (!bool(dest.get_value()))
dest.get_value().emplace();
@@ -395,10 +433,9 @@ namespace wire_read
return (is_required() && !read_) ? field_.name : nullptr;
}
//! Set all entries in `map` related to this field (expand variant types!).
template<std::size_t N>
std::size_t set_mapping(std::size_t index, wire::reader::key_map (&map)[N])
std::size_t set_mapping(std::size_t index, wire::reader::key_map (&map)[N]) noexcept
{
our_index_ = index;
map[index].id = field_.id();
@@ -415,7 +452,7 @@ namespace wire_read
if (read_)
throw_exception(wire::error::schema::invalid_key, "duplicate", {std::addressof(field_.name), 1});
unpack_field(index - our_index_, source, field_);
unpack_field(source, field_);
read_ = true;
return 1 + is_required();
}
@@ -432,13 +469,11 @@ namespace wire_read
// `expand_tracker_map` writes all `tracker` types to a table
template<std::size_t N>
inline constexpr std::size_t expand_tracker_map(std::size_t index, const wire::reader::key_map (&)[N])
{
return index;
}
inline void expand_tracker_map(std::size_t index, const wire::reader::key_map (&)[N]) noexcept
{}
template<std::size_t N, typename T, typename... U>
inline void expand_tracker_map(std::size_t index, wire::reader::key_map (&map)[N], tracker<T>& head, tracker<U>&... tail)
inline void expand_tracker_map(std::size_t index, wire::reader::key_map (&map)[N], tracker<T>& head, tracker<U>&... tail) noexcept
{
expand_tracker_map(head.set_mapping(index, map), map, tail...);
}
@@ -448,15 +483,16 @@ namespace wire_read
{
static constexpr const std::size_t total_subfields = wire::sum(fields.count()...);
static_assert(total_subfields < 100, "algorithm uses too much stack space and linear searching");
static_assert(sizeof...(T) <= total_subfields, "subfield parameter pack size mismatch");
std::size_t state = source.start_object();
std::size_t required = wire::sum(std::size_t(fields.is_required())...);
wire::reader::key_map map[total_subfields] = {};
wire::reader::key_map map[total_subfields + 1] = {}; // +1 for empty object
expand_tracker_map(0, map, fields...);
std::size_t next = 0;
while (source.key(map, state, next))
while (source.key({map, total_subfields}, state, next))
{
switch (wire::sum(fields.try_read(source, next)...))
{
@@ -473,7 +509,7 @@ namespace wire_read
if (required)
{
const char* missing[] = {fields.name_if_missing()...};
const char* missing[] = {fields.name_if_missing()..., nullptr};
throw_exception(wire::error::schema::missing_key, "", missing);
}
@@ -502,4 +538,10 @@ namespace wire
{
wire_read::object(source, wire_read::tracker<T>{std::move(fields)}...);
}
template<typename R, typename... T>
inline void object_fwd(const std::true_type /*is_read*/, R& source, T&&... fields)
{
wire::object(source, std::forward<T>(fields)...);
}
}
+40 -15
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2020, The Monero Project
// Copyright (c) 2021-2024, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
@@ -27,17 +27,24 @@
#pragma once
#include <cstdint>
#include <type_traits>
#include <utility>
#define WIRE_DECLARE_BLOB(type) \
template<> \
struct is_blob<type> \
: std::true_type \
#define WIRE_DECLARE_BLOB_NS(type) \
template<> \
struct is_blob<type> \
: std::true_type \
{}
#define WIRE_DECLARE_BLOB_NS(type) \
namespace wire { WIRE_DECLARE_BLOB(type); }
#define WIRE_DECLARE_BLOB(type) \
namespace wire { WIRE_DECLARE_BLOB_NS(type); }
#define WIRE_DECLARE_OPTIONAL_ROOT(type) \
template<> \
struct is_optional_root<type> \
: std::true_type \
{}
namespace wire
{
@@ -55,20 +62,32 @@ namespace wire
template<typename T>
using unwrap_reference_t = typename unwrap_reference<T>::type;
template<typename T>
struct remove_const_key
{
using type = T;
};
template<typename K, typename V>
struct remove_const_key<std::pair<const K, V>>
{
using type = std::pair<K, V>;
};
/*! Mark `T` as an array for writing, and reading when
`default_min_element_size<T::value_type>::value != 0`. See `array_` in
`wrapper/array.h`. */
`default_min_element_size<T::value_type>::value != 0`. See `array_` in
`wrapper/array.h`. */
template<typename T>
struct is_array : std::false_type
{};
/*! Mark `T` as fixed binary data for reading+writing. Concept requirements
for reading:
* `T` must be compatible with `epee::as_mut_byte_span` (`std::is_pod<T>`
and no padding).
Concept requirements for writing:
* `T` must be compatible with `epee::as_byte_span` (std::is_pod<T>` and
no padding). */
for reading:
* `T` must be compatible with `epee::as_mut_byte_span` (`std::is_pod<T>`
and no padding).
Concept requirements for writing:
* `T` must be compatible with `epee::as_byte_span` (std::is_pod<T>` and
no padding). */
template<typename T>
struct is_blob : std::false_type
{};
@@ -84,6 +103,12 @@ namespace wire
: is_array<T> // all array types in old output engine were optional when empty
{};
//! When `T` is being read as root object, allow an empty read buffer.
template<typename T>
struct is_optional_root
: std::is_empty<T>
{};
//! A constraint for `wire_read::array` where a max of `N` elements can be read.
template<std::size_t N>
struct max_element_count
+5 -5
View File
@@ -26,10 +26,10 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(monero-lws-wire_sources variant.cpp)
set(monero-lws-wire_headers variant.h)
set(lwsf-wire_sources variant.cpp)
set(lwsf-wire_headers variant.h)
add_library(monero-lws-wire-wrapper ${monero-lws-wire_sources} ${monero-lws-wire_headers})
target_include_directories(monero-lws-wire-wrapper PUBLIC "${LMDB_INCLUDE}")
target_link_libraries(monero-lws-wire-wrapper PRIVATE monero::libraries)
add_library(lwsf-wire-wrapper ${lwsf-wire_sources} ${lwsf-wire_headers})
target_include_directories(lwsf-wire-wrapper PUBLIC "${LMDB_INCLUDE}")
target_link_libraries(lwsf-wire-wrapper PRIVATE monero::libraries)