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
+13
View File
@@ -40,22 +40,28 @@ set(MONERO_LIBRARIES
serialization
lmdb_lib
net
checkpoints
hardforks
cryptonote_core
cryptonote_basic
cryptonote_format_utils_basic
ringct
ringct_basic
mnemonics
multisig
blockchain_db
common
lmdb
device
device_trezor
cncrypto
randomx
epee
easylogging
version
unbound
wallet
wallet_api
)
set(MONERO_OPTIONAL unbound)
@@ -64,12 +70,19 @@ set(MONERO_SEARCH_PATHS
"/contrib/epee/src"
"/external/db_drivers/liblmdb"
"/external/easylogging++"
"/lib"
"/src"
"src/checkpoints"
"/src/crypto"
"/src/cryptonote_basic"
"/src/cryptonote_core"
"/src/device"
"/src/device_trezor"
"/src/hardforks"
"/src/lmdb"
"/src/ringct"
"/src/rpc"
"/src/wallet/api"
)
-28
View File
@@ -1,28 +0,0 @@
BSD 3-Clause License
Copyright (c) 2024, The Monero Project
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
+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)