mirror of
https://github.com/wownero/lwsf-wow.git
synced 2026-10-17 07:21:28 +02:00
Update to use boost::beast (websocket transition). (#4)
This commit is contained in:
@@ -8,35 +8,96 @@ on:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
APT_INSTALL_LINUX: 'sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev libprotobuf-dev protobuf-compiler'
|
||||
APT_INSTALL_LINUX: 'apt -y install build-essential cmake git libboost-all-dev miniupnpc libunbound-dev libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev libprotobuf-dev protobuf-compiler'
|
||||
APT_SET_CONF: |
|
||||
echo "Acquire::Retries \"3\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom
|
||||
echo "Acquire::http::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom
|
||||
echo "Acquire::ftp::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom
|
||||
BREW_INSTALL_MAC: 'HOMEBREW_NO_AUTO_UPDATE=1 brew install boost hidapi openssl zmq libpgm miniupnpc expat libunwind-headers protobuf unbound'
|
||||
CONFIGURE: 'cmake -B "${{github.workspace}}/lwsf/build" -DCMAKE_BUILD_TYPE=Release -DMONERO_SOURCE_DIR="${{github.workspace}}/monero" -DLWSF_MASTER_ENABLE=ON -DLWSF_BUILD_TESTS=ON "${{github.workspace}}/lwsf"'
|
||||
BUILD: 'cmake --build "${{github.workspace}}/lwsf/build" -j$(nproc)'
|
||||
RUN: 'ctest --test-dir "${{github.workspace}}/lwsf/build"'
|
||||
CONFIGURE: '-B "$GITHUB_WORKSPACE/lwsf/build" -DCMAKE_BUILD_TYPE=Release -DMONERO_SOURCE_DIR="$GITHUB_WORKSPACE/monero" -DLWSF_BUILD_TESTS=ON -S "$GITHUB_WORKSPACE/lwsf"'
|
||||
BUILD: 'cmake --build "$GITHUB_WORKSPACE/lwsf/build" -j$(nproc)'
|
||||
RUN: 'ctest --test-dir "$GITHUB_WORKSPACE/lwsf/build" --output-on-failure'
|
||||
|
||||
jobs:
|
||||
unix-build-and-test:
|
||||
sanitizer:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
sanitizer: [address, thread, undefined]
|
||||
steps:
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
${{env.APT_SET_CONF}}
|
||||
sudo apt update
|
||||
sudo ${{env.APT_INSTALL_LINUX}}
|
||||
- name: Checkout Monero Source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: monero-project/monero
|
||||
path: ${{github.workspace}}/monero
|
||||
submodules: recursive
|
||||
- name: Checkout LWSF Source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{github.workspace}}/lwsf
|
||||
submodules: recursive
|
||||
- name: Configure LWSF
|
||||
run: cmake -DLWSF_SANITIZER=${{matrix.sanitizer}} -DLWSF_MASTER_ENABLE=ON ${{env.CONFIGURE}}
|
||||
- name: Build LWSF
|
||||
run: ${{env.BUILD}}
|
||||
- name: Run Tests
|
||||
run: ${{env.RUN}}
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{matrix.distro}}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro: [archlinux:base-devel, debian:bookworm-slim, debian:trixie-slim]
|
||||
steps:
|
||||
- name: Install Dependencies (Debian/apt)
|
||||
if: matrix.distro == 'debian:bookworm-slim' || matrix.distro == 'debian:trixie-slim'
|
||||
run: |
|
||||
apt update
|
||||
${{env.APT_INSTALL_LINUX}}
|
||||
- name: Install Dependencies (Arch/pacman)
|
||||
if: matrix.distro == 'archlinux:base-devel'
|
||||
run: |
|
||||
sudo pacman --noconfirm -Syu
|
||||
sudo pacman --noconfirm -S cmake boost boost-libs git hidapi libsodium miniupnpc openssl unbound zeromq
|
||||
- name: Checkout Monero Source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: monero-project/monero
|
||||
path: ${{github.workspace}}/monero/
|
||||
submodules: recursive
|
||||
- name: Checkout LWS Source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{github.workspace}}/lwsf
|
||||
- name: Configure LWS
|
||||
run: cmake -DLWSF_MASTER_ENABLE=ON ${{env.CONFIGURE}}
|
||||
- name: Build LWS
|
||||
run: ${{env.BUILD}}
|
||||
- name: Run Tests
|
||||
run: ${{env.RUN}}
|
||||
unix:
|
||||
runs-on: ${{matrix.os}}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
branch: [master, release-v0.18]
|
||||
os: [macos-15, macos-26, ubuntu-22.04, ubuntu-24.04]
|
||||
steps:
|
||||
- name: set apt conf (Debian based Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ${{env.APT_SET_CONF}}
|
||||
- name: update apt (Debian based Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt update
|
||||
- name: Install dependencies (Debian based Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ${{env.APT_INSTALL_LINUX}}
|
||||
- name: Install Dependencies (Ubuntu)
|
||||
if: matrix.os == 'ubuntu-22.04' || matrix.os == 'ubuntu-24.04'
|
||||
run: |
|
||||
${{env.APT_SET_CONF}}
|
||||
sudo apt update
|
||||
sudo ${{env.APT_INSTALL_LINUX}}
|
||||
- name: Install dependencies (MacOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
if: matrix.os == 'macos-15' || matrix.os == 'macos-26'
|
||||
run: ${{env.BREW_INSTALL_MAC}}
|
||||
- name: Checkout Monero Source
|
||||
uses: actions/checkout@v4
|
||||
@@ -44,18 +105,23 @@ jobs:
|
||||
repository: monero-project/monero
|
||||
path: ${{github.workspace}}/monero/
|
||||
submodules: recursive
|
||||
ref: ${{matrix.branch}}
|
||||
- name: Checkout LWSF Source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{github.workspace}}/lwsf
|
||||
submodules: recursive
|
||||
- name: Configure LWS
|
||||
run: ${{env.CONFIGURE}}
|
||||
- name: Configure LWS (master)
|
||||
if: matrix.branch == 'master'
|
||||
run: cmake -DLWSF_MASTER_ENABLE=ON ${{env.CONFIGURE}}
|
||||
- name: Configure LWS (release)
|
||||
if: matrix.branch == 'release-v0.18'
|
||||
run: cmake ${{env.CONFIGURE}}
|
||||
- name: Build LWSF
|
||||
run: ${{env.BUILD}}
|
||||
- name: Run Tests
|
||||
run: ${{env.RUN}}
|
||||
windows-build-and-test:
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
@@ -90,7 +156,7 @@ jobs:
|
||||
path: ${{github.workspace}}/lwsf
|
||||
submodules: recursive
|
||||
- name: Configure LWS
|
||||
run: ${{env.CONFIGURE}}
|
||||
run: cmake -DLWSF_MASTER_ENABLE=ON ${{env.CONFIGURE}}
|
||||
- name: Build LWSF
|
||||
run: ${{env.BUILD}}
|
||||
- name: Run Tests
|
||||
|
||||
@@ -34,6 +34,16 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
option(LWSF_BUILD_TESTS "Build Tests" OFF)
|
||||
option(LWSF_SANITIZER "Use specific sanitizer" OFF)
|
||||
if(NOT LWSF_SANITIZER STREQUAL "OFF")
|
||||
if (MSVC)
|
||||
message(FATAL_ERROR "Cannot sanitize with MSVC")
|
||||
else()
|
||||
message(STATUS "Using sanitizer=${LWSF_SANITIZER}")
|
||||
add_compile_options("-fsanitize=${LWSF_SANITIZER}")
|
||||
add_link_options("-fsanitize=${LWSF_SANITIZER}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT MONERO_SOURCE_DIR)
|
||||
message(FATAL_ERROR "The argument -DMONERO_SOURCE_DIR must specify a location of a monero source tree")
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
include_directories(.)
|
||||
add_subdirectory(net)
|
||||
add_subdirectory(wire)
|
||||
|
||||
set(lwsf_sources
|
||||
@@ -89,6 +90,7 @@ target_link_libraries(lwsf-api
|
||||
PUBLIC
|
||||
monero::libraries
|
||||
PRIVATE
|
||||
lwsf-net
|
||||
lwsf-wire
|
||||
lwsf-wire-json
|
||||
lwsf-wire-msgpack
|
||||
|
||||
+18
-15
@@ -32,6 +32,7 @@
|
||||
#include <utility>
|
||||
#include "backend.h"
|
||||
#include "cryptonote_basic/cryptonote_basic_impl.h"
|
||||
#include "error.h"
|
||||
|
||||
namespace lwsf { namespace internal {
|
||||
|
||||
@@ -66,9 +67,11 @@ namespace lwsf { namespace internal {
|
||||
error_ = Status_Ok;
|
||||
}
|
||||
|
||||
address_book::address_book(std::shared_ptr<backend::wallet> data)
|
||||
: data_(std::move(data)), addresses_(), error_string_(), error_(Status_Ok)
|
||||
{}
|
||||
address_book::address_book(const net::wallet_io& data)
|
||||
: data_(data), addresses_(), error_string_(), error_(Status_Ok)
|
||||
{
|
||||
LWSF_VERIFY(data_.wallet);
|
||||
}
|
||||
|
||||
address_book::~address_book()
|
||||
{
|
||||
@@ -80,7 +83,7 @@ namespace lwsf { namespace internal {
|
||||
clear_status();
|
||||
|
||||
cryptonote::address_parse_info info;
|
||||
if(!cryptonote::get_account_address_from_str(info, data_->get_net_type(), dst_addr))
|
||||
if(!cryptonote::get_account_address_from_str(info, data_.wallet->get_net_type(), dst_addr))
|
||||
{
|
||||
error_string_ = "Invalid destination address";
|
||||
error_ = Invalid_Address;
|
||||
@@ -99,8 +102,8 @@ namespace lwsf { namespace internal {
|
||||
addresses_.push_back(row.get());
|
||||
row.release();
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
data_->primary.addressbook.push_back(
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
data_.wallet->primary.addressbook.push_back(
|
||||
backend::address_book_entry{dst_addr, payment_id, description}
|
||||
);
|
||||
|
||||
@@ -120,11 +123,11 @@ namespace lwsf { namespace internal {
|
||||
addresses_[rowId] = nullptr;
|
||||
addresses_.erase(addresses_.begin() + rowId);
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
if (data_->primary.addressbook.size() <= rowId)
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
if (data_.wallet->primary.addressbook.size() <= rowId)
|
||||
return false;
|
||||
|
||||
data_->primary.addressbook.erase(data_->primary.addressbook.begin() + rowId);
|
||||
data_.wallet->primary.addressbook.erase(data_.wallet->primary.addressbook.begin() + rowId);
|
||||
force.release();
|
||||
return true;
|
||||
}
|
||||
@@ -143,11 +146,11 @@ namespace lwsf { namespace internal {
|
||||
index, destroy->getAddress(), destroy->getPaymentId(), description
|
||||
};
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
if (data_->primary.addressbook.size() <= index)
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
if (data_.wallet->primary.addressbook.size() <= index)
|
||||
return false;
|
||||
|
||||
data_->primary.addressbook[index].description = description;
|
||||
data_.wallet->primary.addressbook[index].description = description;
|
||||
force.release();
|
||||
return true;
|
||||
}
|
||||
@@ -158,12 +161,12 @@ namespace lwsf { namespace internal {
|
||||
{
|
||||
clear_addresses(addresses_);
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
addresses_.resize(data_->primary.addressbook.size());
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
addresses_.resize(data_.wallet->primary.addressbook.size());
|
||||
|
||||
for (std::size_t i = 0; i < addresses_.size(); ++i)
|
||||
{
|
||||
const auto& current = data_->primary.addressbook[i];
|
||||
const auto& current = data_.wallet->primary.addressbook[i];
|
||||
addresses_[i] = new Monero::AddressBookRow{
|
||||
i, current.address, current.payment_id, current.description
|
||||
};
|
||||
|
||||
+5
-5
@@ -33,14 +33,14 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "lws_frontend.h"
|
||||
#include "net/wallet_io.h"
|
||||
#include "wallet/api/wallet2_api.h" // monero/src
|
||||
|
||||
namespace lwsf { namespace internal {
|
||||
namespace backend { struct wallet; }
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
class address_book final : public ::Monero::AddressBook
|
||||
{
|
||||
const std::shared_ptr<backend::wallet> data_;
|
||||
const net::wallet_io data_;
|
||||
std::vector<Monero::AddressBookRow*> addresses_;
|
||||
std::string error_string_;
|
||||
ErrorCode error_;
|
||||
@@ -49,7 +49,7 @@ namespace lwsf { namespace internal {
|
||||
|
||||
public:
|
||||
|
||||
explicit address_book(std::shared_ptr<backend::wallet> data);
|
||||
explicit address_book(const net::wallet_io& data);
|
||||
|
||||
address_book(const address_book&) = delete;
|
||||
address_book(address_book&&) = delete;
|
||||
|
||||
+764
-378
File diff suppressed because it is too large
Load Diff
+65
-16
@@ -29,6 +29,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
@@ -36,6 +38,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
@@ -51,6 +54,7 @@
|
||||
#include "cryptonote_basic/account.h" // monero/src
|
||||
#include "lwsf_config.h"
|
||||
#include "lwsf_rpc.h"
|
||||
#include "net/context.h"
|
||||
#include "ringct/rctTypes.h" // monero/src
|
||||
#include "wallet/api/wallet2_api.h" // monero/src
|
||||
#include "wire/fwd.h"
|
||||
@@ -233,11 +237,16 @@ namespace lwsf { namespace internal { namespace backend
|
||||
//! All functions should provide the strong-exception guarantee
|
||||
struct wallet
|
||||
{
|
||||
Monero::WalletListener* listener;
|
||||
const std::unique_ptr<rpc::http_client> client;
|
||||
// Everything uses `sync` for synchronization unless noted
|
||||
|
||||
Monero::WalletListener* listener; //!< Use `sync_listener`
|
||||
boost::asio::io_context::strand strand; //<! useful in preventing contention on `sync`
|
||||
http::client client;
|
||||
account primary;
|
||||
std::string client_prefix;
|
||||
std::vector<std::uint64_t> per_byte_fee; //!< by priority level
|
||||
std::vector<std::function<void(expect<bool>)>> login_queue; //!< use `sync_queue`
|
||||
std::vector<std::function<void(std::error_code)>> refresh_queue; //!< use `sync_queue`
|
||||
std::error_code refresh_error; //!< Cached because `refresh(...)` is rate limited
|
||||
std::error_code subaddress_error; //!< Errors with subaddresses (not via lookahead)
|
||||
std::error_code lookahead_error; //!< warnings/errors of `server_lookahead` value
|
||||
@@ -248,11 +257,35 @@ namespace lwsf { namespace internal { namespace backend
|
||||
config::lookahead server_lookahead; //!< Status of lookahead server-side
|
||||
mutable boost::mutex sync;
|
||||
boost::mutex sync_listener;
|
||||
boost::mutex sync_refresh;
|
||||
boost::mutex sync_queue;
|
||||
bool passed_login;
|
||||
bool probed_lookahead; //!< True iff queries were done to determine lookahead re-sync
|
||||
|
||||
wallet();
|
||||
wallet(boost::asio::io_context& io);
|
||||
|
||||
//! Push `f` to `dest` with thread synchronization
|
||||
template<typename T, typename F>
|
||||
bool push(std::vector<std::function<T>>& dest, F&& f)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{sync_queue};
|
||||
const bool rc = dest.empty();
|
||||
dest.push_back(std::forward<F>(f));
|
||||
return rc;
|
||||
}
|
||||
|
||||
//! Call all functions in `waiting` with thread synchronization
|
||||
template<typename T, typename... U>
|
||||
void run(std::vector<std::function<T>>& waiting, const U&... args)
|
||||
{
|
||||
std::vector<std::function<T>> ready;
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{sync_queue};
|
||||
ready.swap(waiting);
|
||||
}
|
||||
for (const auto& f : ready)
|
||||
f(args...);
|
||||
}
|
||||
|
||||
|
||||
// `sync` mutex is NOT acquired for this group
|
||||
|
||||
@@ -265,7 +298,7 @@ namespace lwsf { namespace internal { namespace backend
|
||||
|
||||
// End GROUP
|
||||
|
||||
// `sync` mutex IS acquired for this group (until end). Do not hold this mutex
|
||||
// `sync` mutex IS acquired for this group Do not hold this mutex
|
||||
|
||||
//! Serialize `this` wallet to msgpack. Locks contents.
|
||||
expect<epee::byte_slice> to_bytes() const;
|
||||
@@ -273,29 +306,45 @@ namespace lwsf { namespace internal { namespace backend
|
||||
//! De-serialize `this` from msgpack. Locks+replaces contents.
|
||||
std::error_code from_bytes(epee::byte_slice source);
|
||||
|
||||
// End GROUP
|
||||
|
||||
// `sync` is acquired in a `asio::post` function - thread-safe and non-blocking until end
|
||||
|
||||
/*! Attempt login and check lookahead status. Updates `lookahead_error`.
|
||||
\return No errors if login succeeded, and true if new account */
|
||||
expect<bool> login_is_new();
|
||||
static void login_is_new(std::shared_ptr<wallet> self, std::function<void(expect<bool>)>);
|
||||
|
||||
/*! Attempt login and check lookahead status. Updates `lookahead_error`.
|
||||
\return No errors if login succeeded. */
|
||||
std::error_code login() { return login_is_new().error(); }
|
||||
template<typename F>
|
||||
static void login(std::shared_ptr<wallet> self, F f)
|
||||
{
|
||||
login_is_new(std::move(self), [f = std::move(f)] (expect<bool> r) mutable { f(r.error()); });
|
||||
}
|
||||
|
||||
//! Refreshes txes information. Strong exception guarantee.
|
||||
std::error_code refresh(bool mandatory = false);
|
||||
static void refresh(std::shared_ptr<wallet> self, bool mandatory, std::function<void(std::error_code)>);
|
||||
|
||||
//! Notify server that new major accounts need to be watched.
|
||||
std::error_code register_subaccount(std::uint32_t maj_i);
|
||||
static void register_subaccount(std::shared_ptr<wallet> self, std::uint32_t maj_i, std::function<void(std::error_code)> f);
|
||||
|
||||
//! Notify server that new minor accounts need to be watched.
|
||||
std::error_code register_subaddress(std::uint32_t maj_i, std::uint32_t min_i);
|
||||
//! Notify server that new minor accounts need to be watched. Do not hold `self->sync`.
|
||||
static void register_subaddress(std::shared_ptr<wallet> self, std::uint32_t maj_i, std::uint32_t min_i, std::function<void(std::error_code)>);
|
||||
|
||||
//! Modify local and possibly server lookahead
|
||||
std::error_code set_lookahead(std::uint32_t major, std::uint32_t minor);
|
||||
//! Modify local and possibly server lookahead. Do not hold `self->sync`.
|
||||
static void set_lookahead(std::shared_ptr<wallet> self, std::uint32_t major, std::uint32_t minor, std::function<void(std::error_code)>);
|
||||
|
||||
expect<rpc::import_response> restore_height(const std::uint64_t height);
|
||||
static void restore_height_raw(std::shared_ptr<wallet> self, const std::uint64_t height, std::function<void(expect<rpc::import_response>)>);
|
||||
|
||||
expect<std::vector<rpc::random_outputs>> get_decoys(const rpc::get_random_outs_request& req);
|
||||
std::error_code send_tx(epee::byte_slice tx_bytes);
|
||||
template<typename F>
|
||||
static void restore_height(std::shared_ptr<wallet> self, const std::uint64_t height, F f)
|
||||
{
|
||||
restore_height_raw(std::move(self), height, [f = std::move(f)] (auto r) mutable { f(r.error()); });
|
||||
}
|
||||
|
||||
using decoys_callable = void(expect<std::vector<rpc::random_outputs>>);
|
||||
static void get_decoys(std::shared_ptr<wallet> self, rpc::get_random_outs_request&& req, std::function<decoys_callable> f);
|
||||
|
||||
static void send_tx(std::shared_ptr<wallet> slef, epee::byte_slice tx_bytes, std::function<void(std::error_code)> f);
|
||||
};
|
||||
}}} // lwsf // internal // backend
|
||||
|
||||
+18
-1
@@ -27,6 +27,9 @@
|
||||
|
||||
#include "error.h"
|
||||
|
||||
#include <cstring>
|
||||
#include "common/error.h" // monero/src
|
||||
|
||||
namespace lwsf
|
||||
{
|
||||
const char* get_string(const error value) noexcept
|
||||
@@ -57,10 +60,14 @@ namespace lwsf
|
||||
return "server has subaddresses disabled";
|
||||
case error::subaddr_local:
|
||||
return "lwsf (local) limits on subaddresses too small";
|
||||
case error::subaddr_upgrade:
|
||||
return "server does not support newer subaddr commands";
|
||||
case error::unexpected_userinfo:
|
||||
return "Unexpected user+pass field in URL";
|
||||
case error::unexpected_nullptr:
|
||||
return "Unexpected nullptr";
|
||||
case error::unknown_exception:
|
||||
return "Unknown exception";
|
||||
|
||||
default:
|
||||
break;
|
||||
@@ -74,7 +81,7 @@ namespace lwsf
|
||||
{
|
||||
virtual const char* name() const noexcept override final
|
||||
{
|
||||
return "lws::error_category()";
|
||||
return "lwsf::error_category()";
|
||||
}
|
||||
|
||||
virtual std::string message(int value) const override final
|
||||
@@ -85,4 +92,14 @@ namespace lwsf
|
||||
static const category instance{};
|
||||
return instance;
|
||||
}
|
||||
|
||||
void throw_invalid_argument(const int line, const char* file)
|
||||
{
|
||||
if (!file)
|
||||
file = "";
|
||||
char const* const end = std::strrchr(file, '/');
|
||||
if (end)
|
||||
file = end + 1;
|
||||
throw std::invalid_argument{"Failed pre-condition at " + std::string{file} + ":" + std::to_string(line)};
|
||||
}
|
||||
} // lwsf
|
||||
|
||||
+11
@@ -28,6 +28,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
|
||||
#define LWSF_VERIFY(x) \
|
||||
do \
|
||||
{ \
|
||||
if (!(x)) \
|
||||
::lwsf::throw_invalid_argument(__LINE__, __FILE__); \
|
||||
} while (0)
|
||||
|
||||
namespace lwsf
|
||||
{
|
||||
@@ -46,8 +54,10 @@ namespace lwsf
|
||||
subaddr_ahead, //!< Server limits on subaddresses affects lookahead
|
||||
subaddr_disabled, //!< Server has subaddresses disabled
|
||||
subaddr_local, //!< Local limits on subaddresses too small
|
||||
subaddr_upgrade, //!< Upgrade server for server-side limits
|
||||
unexpected_userinfo,//!< Unexpected user+pass provided
|
||||
unexpected_nullptr, //!< Expected non-nullptr
|
||||
unknown_exception //!< Unknown exception occured
|
||||
};
|
||||
|
||||
//! \return Error message string.
|
||||
@@ -62,6 +72,7 @@ namespace lwsf
|
||||
return std::error_code{int(value), error_category()};
|
||||
}
|
||||
|
||||
[[noreturn]] void throw_invalid_argument(int line, const char* file);
|
||||
} // lwsf
|
||||
|
||||
namespace std
|
||||
|
||||
@@ -47,6 +47,9 @@ namespace lwsf { namespace config
|
||||
constexpr const lookahead default_lookahead{50, 200};
|
||||
constexpr const lookahead default_minimal_lookahead{5, 15};
|
||||
constexpr const std::string_view default_primary_name{"Primary account"};
|
||||
constexpr const std::size_t http_body_limit = 25 * 1024 * 1024; // 25 MiB
|
||||
constexpr const std::size_t http_parser_buffer_size = 16 * 1024; // 16 KiB
|
||||
constexpr const unsigned http_version = 11;
|
||||
constexpr const std::size_t initial_buffer_size = 1024 * 64; // 64 KiB
|
||||
constexpr const std::size_t max_file_read_size = 50 * 1024 * 1024; // 50 MiB
|
||||
constexpr const std::size_t max_inputs_in_rpc = 512;
|
||||
@@ -58,4 +61,5 @@ namespace lwsf { namespace config
|
||||
constexpr const std::chrono::seconds refresh_interval{30};
|
||||
constexpr const std::chrono::seconds refresh_interval_min{5};
|
||||
constexpr const std::chrono::seconds rpc_timeout{30};
|
||||
constexpr const bool use_subaddresses = true; //!< Disables subaddress rpc probing
|
||||
}} // lwsf // config
|
||||
|
||||
@@ -54,81 +54,6 @@
|
||||
|
||||
namespace lwsf { namespace internal { namespace rpc
|
||||
{
|
||||
namespace
|
||||
{
|
||||
//! \return Error message string.
|
||||
const char* get_string(error value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
|
||||
case error::none:
|
||||
return "No rpc errors";
|
||||
case error::no_response:
|
||||
return "No response from HTTP server";
|
||||
case error::invalid_code:
|
||||
return "Invalid status code from HTTP server";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//! \return Category for `error`.
|
||||
const std::error_category& error_category() noexcept
|
||||
{
|
||||
struct category final : std::error_category
|
||||
{
|
||||
virtual const char* name() const noexcept override final
|
||||
{
|
||||
return "lwsf::internal::rpc::error_category()";
|
||||
}
|
||||
|
||||
virtual std::string message(int value) const override final
|
||||
{
|
||||
char const * const msg = get_string(error(value));
|
||||
if (msg)
|
||||
return msg;
|
||||
return "HTTP error code " + std::to_string(value);
|
||||
}
|
||||
};
|
||||
static const category instance{};
|
||||
return instance;
|
||||
}
|
||||
|
||||
expect<std::string> invoke_payload(http_client& client, const boost::string_ref prefix, boost::string_ref endpoint, const epee::byte_slice payload)
|
||||
{
|
||||
static const epee::net_utils::http::fields_list headers{
|
||||
{"Content-Type", "application/json; charset=utf-8"}
|
||||
};
|
||||
|
||||
if (!client.is_connected())
|
||||
{
|
||||
if (!client.connect(config::connect_timeout))
|
||||
return {error::no_response};
|
||||
}
|
||||
|
||||
std::string real;
|
||||
if (!prefix.empty())
|
||||
{
|
||||
real = std::string{prefix} + std::string{endpoint};
|
||||
endpoint = real;
|
||||
}
|
||||
|
||||
const epee::net_utils::http::http_response_info* response = nullptr;
|
||||
if (!client.invoke(endpoint, "POST", {reinterpret_cast<const char*>(payload.data()), payload.size()}, config::rpc_timeout, std::addressof(response), headers))
|
||||
return {error::no_response};
|
||||
if (!response)
|
||||
return {error::no_response};
|
||||
if(response->m_response_code == 200 || response->m_response_code == 201)
|
||||
return response->m_body;
|
||||
|
||||
if (response->m_response_code <= 0 || std::numeric_limits<int>::max() < response->m_response_code)
|
||||
return {error::invalid_code};
|
||||
return {error(int(response->m_response_code))};
|
||||
}
|
||||
|
||||
void write_bytes(wire::writer& dest, const empty&)
|
||||
{ wire::object(dest); }
|
||||
void read_bytes(wire::reader& source, empty&)
|
||||
|
||||
+37
-43
@@ -30,7 +30,9 @@
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/core/demangle.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/thread/locks.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
#include <ctime>
|
||||
#include <system_error>
|
||||
@@ -40,57 +42,54 @@
|
||||
#include "byte_stream.h" // moneor/contrib/epee/include
|
||||
#include "common/expect.h" // monero/src
|
||||
#include "crypto/crypto.h" // monero/src
|
||||
#include "error.h"
|
||||
#include "lwsf_config.h"
|
||||
#include "net/http.h"
|
||||
#include "ringct/rctTypes.h" // monero/src
|
||||
#include "wire/basic_value.h"
|
||||
#include "wire/fwd.h"
|
||||
#include "wire/json.h"
|
||||
#include "wire/traits.h"
|
||||
|
||||
namespace epee { namespace net_utils
|
||||
{
|
||||
class blocked_mode_client;
|
||||
namespace http { template<typename> class http_simple_client_template; }
|
||||
}}
|
||||
|
||||
namespace lwsf { namespace internal { namespace rpc
|
||||
{
|
||||
using max_subaddrs = wire::max_element_count<16384>;
|
||||
using http_client = epee::net_utils::http::http_simple_client_template<
|
||||
epee::net_utils::blocked_mode_client
|
||||
>;
|
||||
struct daemon_status;
|
||||
|
||||
enum class error : int
|
||||
template<typename T, typename F>
|
||||
struct unpacker
|
||||
{
|
||||
none = 0, no_response = -1, invalid_code = -2 /* Otherwise HTTP error code */
|
||||
T* out;
|
||||
F f;
|
||||
|
||||
void operator()(std::error_code error, epee::byte_slice response)
|
||||
{
|
||||
LWSF_VERIFY(out);
|
||||
if (!error)
|
||||
{
|
||||
error = wire::json::from_bytes({reinterpret_cast<const char*>(response.data()), response.size()}, *out);
|
||||
if (error)
|
||||
MERROR("Failed to unpack " << boost::core::demangle(typeid(T).name()) << ": " << error.message());
|
||||
}
|
||||
f(error);
|
||||
}
|
||||
};
|
||||
|
||||
const std::error_category& error_category() noexcept;
|
||||
inline std::error_code make_error_code(const error value) noexcept
|
||||
|
||||
template<typename T, typename U, typename F>
|
||||
void invoke_async(const http::client& client, const T& in, U* out, F f)
|
||||
{
|
||||
return std::error_code{int(value), error_category()};
|
||||
}
|
||||
|
||||
//! Send `payload` to `client` at uri `endpoint`, and \return payload response
|
||||
expect<std::string> invoke_payload(http_client& client, boost::string_ref prefix, boost::string_ref endpoint, epee::byte_slice payload);
|
||||
|
||||
template<typename F, typename G>
|
||||
expect<F> invoke(http_client& client, boost::string_ref prefix, const G& in)
|
||||
{
|
||||
epee::byte_stream sink{};
|
||||
std::error_code error = wire::json::to_bytes(sink, in);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
expect<std::string> result = invoke_payload(client, prefix, F::endpoint(), epee::byte_slice{std::move(sink)});
|
||||
if (!result)
|
||||
return result.error();
|
||||
|
||||
F out{};
|
||||
error = wire::json::from_bytes(epee::to_span(*result), out);
|
||||
if (error)
|
||||
return error;
|
||||
return out;
|
||||
// /daemon_status historically needed to be a post
|
||||
if constexpr (!std::is_empty<T>{} || std::is_same<U, daemon_status>{})
|
||||
{
|
||||
epee::byte_stream sink{};
|
||||
std::error_code error = wire::json::to_bytes(sink, in);
|
||||
if (error)
|
||||
return f(error);
|
||||
client.post_async(U::endpoint(), epee::byte_slice{std::move(sink)}, unpacker<U, F>{out, std::move(f)});
|
||||
}
|
||||
else
|
||||
client.get_async(U::endpoint(), unpacker<U, F>{out, std::move(f)});
|
||||
}
|
||||
|
||||
struct empty {};
|
||||
@@ -409,6 +408,8 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
import_response() = delete;
|
||||
import_response(import_response&&) = default;
|
||||
import_response(const import_response&) = delete;
|
||||
import_response& operator=(import_response&&) = default;
|
||||
import_response& operator=(const import_response&) = delete;
|
||||
static constexpr const char* endpoint() noexcept { return "/import_wallet_request"; }
|
||||
|
||||
boost::optional<std::string> payment_address;
|
||||
@@ -482,10 +483,3 @@ namespace lwsf { namespace internal { namespace rpc
|
||||
|
||||
WIRE_DECLARE_BLOB(lwsf::internal::rpc::ringct);
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
struct is_error_code_enum<lwsf::internal::rpc::error>
|
||||
: true_type
|
||||
{};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
|
||||
set(lwsf-net_sources context.cpp http.cpp wallet_io.cpp)
|
||||
set(lwsf-net_headers context.h http.h wallet_io.h)
|
||||
|
||||
add_library(lwsf-net ${lwsf-net_sources} ${lwsf-net_headers})
|
||||
target_link_libraries(lwsf-net PRIVATE monero::libraries)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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 "context.h"
|
||||
#include <boost/thread/locks.hpp>
|
||||
#include <boost/thread/thread.hpp>
|
||||
|
||||
namespace lwsf { namespace internal { namespace net
|
||||
{
|
||||
context::context()
|
||||
: restart_(), io_(), sync_()
|
||||
{}
|
||||
|
||||
std::shared_ptr<int> context::restart_asio()
|
||||
{
|
||||
std::shared_ptr<int> out;
|
||||
const boost::lock_guard<boost::mutex> lock{sync_};
|
||||
out = restart_.lock();
|
||||
if (!out || io_.stopped())
|
||||
{
|
||||
out.reset(); // need to restart //
|
||||
while (restart_.lock())
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds{50});
|
||||
io_.restart();
|
||||
out = std::make_shared<int>();
|
||||
restart_ = out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}}} // lwsf // internal // net
|
||||
@@ -0,0 +1,101 @@
|
||||
// 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 <boost/asio/io_context.hpp>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace lwsf { namespace internal { namespace net
|
||||
{
|
||||
template<typename T>
|
||||
struct to_future
|
||||
{
|
||||
struct inner
|
||||
{
|
||||
std::promise<T> promise;
|
||||
inner()
|
||||
: promise()
|
||||
{}
|
||||
};
|
||||
|
||||
std::shared_ptr<inner> inner_;
|
||||
|
||||
to_future()
|
||||
: inner_(std::make_shared<inner>())
|
||||
{}
|
||||
|
||||
void operator()(T value)
|
||||
{
|
||||
LWSF_VERIFY(inner_);
|
||||
inner_->promise.set_value(std::move(value));
|
||||
}
|
||||
};
|
||||
|
||||
struct context
|
||||
{
|
||||
std::weak_ptr<int> restart_;
|
||||
boost::asio::io_context io_;
|
||||
boost::mutex sync_;
|
||||
|
||||
explicit context();
|
||||
|
||||
//! Restart `io_` if needed, and get an object that prevents future restarts.
|
||||
std::shared_ptr<int> restart_asio();
|
||||
|
||||
template<typename T, typename F>
|
||||
T wait_for(F f)
|
||||
{
|
||||
to_future<T> callable{};
|
||||
auto value = callable.inner_->promise.get_future();
|
||||
f(std::move(callable));
|
||||
for (;;)
|
||||
{
|
||||
switch (value.wait_for(std::chrono::seconds{0}))
|
||||
{
|
||||
case std::future_status::deferred:
|
||||
case std::future_status::ready:
|
||||
return value.get();
|
||||
|
||||
case std::future_status::timeout:
|
||||
{
|
||||
const auto restart_lock = restart_asio();
|
||||
io_.run_one_for(std::chrono::milliseconds{100});
|
||||
}
|
||||
break;
|
||||
};
|
||||
}
|
||||
throw std::logic_error{"unreachable"};
|
||||
}
|
||||
};
|
||||
|
||||
}}} // lwsf // internal // net
|
||||
@@ -0,0 +1,688 @@
|
||||
// Copyright (c) 2024, 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 "http.h"
|
||||
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/ip/address_v6.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/ssl/stream.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/beast/core/flat_static_buffer.hpp>
|
||||
#include <boost/beast/http/parser.hpp>
|
||||
#include <boost/beast/http/read.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/write.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/thread/locks.hpp>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
|
||||
#include "byte_stream.h" // monero/controib/epee/include
|
||||
#include "common/error.h" // monero/src
|
||||
#include "error.h"
|
||||
#include "lwsf_config.h"
|
||||
#include "misc_log_ex.h" // monero/contrib/epee/include
|
||||
#include "net/net_utils_base.h"
|
||||
#include "net/socks.h" // monero/src
|
||||
#include "string_tools.h" // monero/contrib/epee/include
|
||||
|
||||
namespace lwsf { namespace internal { namespace http
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const char* get_string(error value) noexcept
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
|
||||
case error::none:
|
||||
return "No rpc errors";
|
||||
case error::timeout:
|
||||
return "No response from HTTP server";
|
||||
case error::invalid_code:
|
||||
return "Invalid status code from HTTP server";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct message
|
||||
{
|
||||
message(epee::byte_slice body, std::string target, std::function<server_response_func>&& notifier, boost::beast::http::verb verb)
|
||||
: body(std::move(body)),
|
||||
notifier(std::move(notifier)),
|
||||
target(std::move(target)),
|
||||
verb(verb)
|
||||
{}
|
||||
|
||||
message(message&&) = default;
|
||||
message(const message& rhs)
|
||||
: body(rhs.body.clone()),
|
||||
notifier(rhs.notifier),
|
||||
target(rhs.target),
|
||||
verb(rhs.verb)
|
||||
{}
|
||||
|
||||
bool is_get() const noexcept { return verb == boost::beast::http::verb::get; }
|
||||
|
||||
epee::byte_slice body;
|
||||
std::function<server_response_func> notifier;
|
||||
std::string target;
|
||||
boost::beast::http::verb verb;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const message& src)
|
||||
{
|
||||
out << src.target;
|
||||
return out;
|
||||
}
|
||||
|
||||
struct stream_body
|
||||
{
|
||||
using value_type = epee::byte_stream;
|
||||
|
||||
struct reader
|
||||
{
|
||||
epee::byte_stream& body_;
|
||||
|
||||
template<bool isRequest, class Fields>
|
||||
reader(boost::beast::http::header<isRequest, Fields>&, value_type& body)
|
||||
: body_(body)
|
||||
{}
|
||||
|
||||
void init(boost::optional<std::uint64_t> const& content_length, boost::system::error_code& ec)
|
||||
{
|
||||
static_assert(
|
||||
std::numeric_limits<std::uint64_t>::max() <= std::numeric_limits<std::size_t>::max()
|
||||
);
|
||||
if (content_length)
|
||||
body_.reserve(*content_length);
|
||||
ec = {};
|
||||
}
|
||||
|
||||
template<class ConstBufferSequence>
|
||||
std::size_t put(ConstBufferSequence const& buffers, boost::system::error_code& ec)
|
||||
{
|
||||
const std::size_t size = boost::asio::buffer_size(buffers);
|
||||
body_.write({reinterpret_cast<const std::uint8_t*>(buffers.data()), size});
|
||||
ec = {};
|
||||
return size;
|
||||
}
|
||||
|
||||
void finish(boost::system::error_code& ec)
|
||||
{
|
||||
ec = {};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct slice_body
|
||||
{
|
||||
using value_type = epee::byte_slice;
|
||||
|
||||
static std::size_t size(const value_type& source) noexcept
|
||||
{
|
||||
return source.size();
|
||||
};
|
||||
|
||||
struct writer
|
||||
{
|
||||
epee::byte_slice body_;
|
||||
|
||||
using const_buffers_type = boost::asio::const_buffer;
|
||||
|
||||
template<bool is_request, typename Fields>
|
||||
explicit writer(boost::beast::http::header<is_request, Fields> const&, value_type const& body)
|
||||
: body_(body.clone())
|
||||
{}
|
||||
|
||||
void init(boost::beast::error_code& ec)
|
||||
{
|
||||
ec = {};
|
||||
}
|
||||
|
||||
boost::optional<std::pair<const_buffers_type, bool>> get(boost::beast::error_code& ec)
|
||||
{
|
||||
ec = {};
|
||||
return {{const_buffers_type{body_.data(), body_.size()}, false}};
|
||||
}
|
||||
};
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
//! \return Category for `error`.
|
||||
const std::error_category& error_category() noexcept
|
||||
{
|
||||
struct category final : std::error_category
|
||||
{
|
||||
virtual const char* name() const noexcept override final
|
||||
{
|
||||
return "lwsf::internal::http::error_category()";
|
||||
}
|
||||
|
||||
virtual std::string message(int value) const override final
|
||||
{
|
||||
char const * const msg = get_string(error(value));
|
||||
if (msg)
|
||||
return msg;
|
||||
return "HTTP error code " + std::to_string(value);
|
||||
}
|
||||
};
|
||||
static const category instance{};
|
||||
return instance;
|
||||
}
|
||||
|
||||
struct client_state
|
||||
{
|
||||
using connect_func =
|
||||
void(std::shared_ptr<client_state>, std::function<void(boost::system::error_code)>);
|
||||
|
||||
boost::beast::flat_static_buffer<config::http_parser_buffer_size> buffer;
|
||||
std::function<connect_func> connect;
|
||||
std::deque<message> outgoing;
|
||||
const std::string host;
|
||||
const std::string prefix;
|
||||
boost::asio::steady_timer timer;
|
||||
boost::asio::io_context::strand strand;
|
||||
boost::asio::ip::tcp::endpoint endpoint;
|
||||
boost::beast::http::request<slice_body> request;
|
||||
const epee::net_utils::ssl_options_t ssl;
|
||||
boost::asio::ssl::context ssl_context;
|
||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sock;
|
||||
boost::optional<boost::beast::http::parser<false, stream_body>> parser;
|
||||
std::size_t iteration;
|
||||
const std::uint16_t port;
|
||||
std::atomic<bool> is_connected;
|
||||
epee::net_utils::ssl_support_t ssl_status;
|
||||
|
||||
client_state(boost::asio::io_context& io, std::string host, std::string prefix, const std::uint16_t port, epee::net_utils::ssl_options_t in, std::function<connect_func> connect)
|
||||
: buffer{},
|
||||
connect(std::move(connect)),
|
||||
outgoing(),
|
||||
host(std::move(host)),
|
||||
prefix(std::move(prefix)),
|
||||
timer(io),
|
||||
strand(io),
|
||||
endpoint(),
|
||||
request{},
|
||||
ssl(std::move(in)),
|
||||
ssl_context(ssl.create_context()),
|
||||
sock(io, ssl_context),
|
||||
parser(),
|
||||
iteration(0),
|
||||
port(port),
|
||||
is_connected(false),
|
||||
ssl_status(ssl.support)
|
||||
{}
|
||||
|
||||
template<typename F>
|
||||
void async_write(F&& callback)
|
||||
{
|
||||
assert(!outgoing.empty());
|
||||
const bool no_body = outgoing.front().body.empty();
|
||||
if (!prefix.empty())
|
||||
outgoing.front().target.insert(0, prefix.data(), prefix.size());
|
||||
|
||||
request = {
|
||||
outgoing.front().verb,
|
||||
outgoing.front().target,
|
||||
config::http_version,
|
||||
std::move(outgoing.front().body)
|
||||
};
|
||||
request.set(boost::beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
||||
if (!no_body)
|
||||
request.set(boost::beast::http::field::content_type, "application/json");
|
||||
|
||||
// Setting Host is tricky. Check for v6 and non-standard ports
|
||||
boost::system::error_code error{};
|
||||
boost::asio::ip::make_address_v6(host, error);
|
||||
const bool https = ssl_status == epee::net_utils::ssl_support_t::e_ssl_support_enabled;
|
||||
if (!error)
|
||||
request.set(boost::beast::http::field::host, "[" + host + "]:" + std::to_string(port));
|
||||
else if ((https && port == 443) || (!https && port == 80))
|
||||
request.set(boost::beast::http::field::host, host);
|
||||
else
|
||||
request.set(boost::beast::http::field::host, host + ":" + std::to_string(port));
|
||||
|
||||
request.prepare_payload();
|
||||
if (https)
|
||||
boost::beast::http::async_write(sock, request, boost::asio::bind_executor(strand, std::forward<F>(callback)));
|
||||
else
|
||||
boost::beast::http::async_write(sock.next_layer(), request, boost::asio::bind_executor(strand, std::forward<F>(callback)));
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void async_read(F&& callback)
|
||||
{
|
||||
assert(sock);
|
||||
assert(!outgoing.empty());
|
||||
parser.emplace();
|
||||
parser->body_limit(config::http_body_limit);
|
||||
if (ssl_status == epee::net_utils::ssl_support_t::e_ssl_support_enabled)
|
||||
boost::beast::http::async_read(sock, buffer, *parser, boost::asio::bind_executor(strand, std::forward<F>(callback)));
|
||||
else
|
||||
boost::beast::http::async_read(sock.next_layer(), buffer, *parser, boost::asio::bind_executor(strand, std::forward<F>(callback)));
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
MWARNING("Closing socket to " << host);
|
||||
|
||||
++iteration;
|
||||
is_connected = false;
|
||||
|
||||
boost::system::error_code ignore{};
|
||||
timer.cancel();
|
||||
sock.next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignore);
|
||||
sock.next_layer().close(ignore);
|
||||
}
|
||||
|
||||
void notify_connection_error()
|
||||
{
|
||||
for (auto& elem : outgoing)
|
||||
{
|
||||
if (elem.notifier)
|
||||
elem.notifier(error::timeout, {});
|
||||
}
|
||||
|
||||
outgoing.clear();
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename F>
|
||||
void set_timeout(std::shared_ptr<client_state> self, const std::chrono::steady_clock::duration timeout, F f)
|
||||
{
|
||||
LWSF_VERIFY(self);
|
||||
|
||||
struct on_timeout
|
||||
{
|
||||
on_timeout() = delete;
|
||||
std::shared_ptr<client_state> self_;
|
||||
F f_;
|
||||
const std::size_t iteration;
|
||||
|
||||
void operator()(const boost::system::error_code error)
|
||||
{
|
||||
LWSF_VERIFY(self_);
|
||||
if (!self_ || error == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
if (iteration < self_->iteration)
|
||||
return;
|
||||
|
||||
MWARNING("Timeout in HTTP attempt to " << self_->host);
|
||||
f_(error);
|
||||
}
|
||||
};
|
||||
|
||||
client_state& me = *self;
|
||||
me.timer.expires_after(timeout);
|
||||
me.timer.async_wait(boost::asio::bind_executor(me.strand, on_timeout{std::move(self), std::move(f), me.iteration}));
|
||||
}
|
||||
|
||||
void set_timeout(std::shared_ptr<client_state> self, const std::chrono::steady_clock::duration timeout)
|
||||
{
|
||||
LWSF_VERIFY(self);
|
||||
|
||||
struct on_timeout
|
||||
{
|
||||
on_timeout() = delete;
|
||||
std::shared_ptr<client_state> self_;
|
||||
|
||||
void operator()(boost::system::error_code error = {}) const
|
||||
{
|
||||
LWSF_VERIFY(self_);
|
||||
assert(self_->strand.running_in_this_thread());
|
||||
self_->close();
|
||||
}
|
||||
};
|
||||
|
||||
set_timeout(self, timeout, on_timeout{self});
|
||||
}
|
||||
|
||||
class client_loop : public boost::asio::coroutine
|
||||
{
|
||||
std::shared_ptr<client_state> self_;
|
||||
|
||||
public:
|
||||
explicit client_loop(std::shared_ptr<client_state> self) noexcept
|
||||
: boost::asio::coroutine(), self_(std::move(self))
|
||||
{}
|
||||
|
||||
void operator()(boost::system::error_code error = {}, std::size_t = 0)
|
||||
{
|
||||
LWSF_VERIFY(self_);
|
||||
|
||||
client_state& self = *self_;
|
||||
assert(self.strand.running_in_this_thread());
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
if (!self.is_connected)
|
||||
{
|
||||
do_connect:
|
||||
BOOST_ASIO_CORO_YIELD self.connect(self_, *this);
|
||||
|
||||
if (!error && self.ssl_status != epee::net_utils::ssl_support_t::e_ssl_support_disabled)
|
||||
{
|
||||
++self.iteration;
|
||||
set_timeout(self_, config::connect_timeout); // socks needs a reset
|
||||
self.ssl.configure(self.sock, boost::asio::ssl::stream_base::client, self.host);
|
||||
|
||||
MDEBUG("Starting SSL handshake to " << self.host << " for HTTP");
|
||||
BOOST_ASIO_CORO_YIELD self.sock.async_handshake(
|
||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>::client,
|
||||
boost::asio::bind_executor(self.strand, std::move(*this))
|
||||
);
|
||||
|
||||
if (error)
|
||||
{
|
||||
MERROR("SSL handshake to " << self.host << " failed: " << error.message());
|
||||
if (self.ssl_status == epee::net_utils::ssl_support_t::e_ssl_support_autodetect)
|
||||
{
|
||||
MINFO("Retrying to << " << self.host << " without ssl (autodetect mode)");
|
||||
self.ssl_status = epee::net_utils::ssl_support_t::e_ssl_support_disabled;
|
||||
error = {};
|
||||
++self.iteration;
|
||||
goto do_connect;
|
||||
}
|
||||
}
|
||||
else
|
||||
self.ssl_status = epee::net_utils::ssl_support_t::e_ssl_support_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
if (error)
|
||||
return self.notify_connection_error();
|
||||
|
||||
++self.iteration;
|
||||
self.is_connected = true;
|
||||
while (!self.outgoing.empty())
|
||||
{
|
||||
set_timeout(self_, config::rpc_timeout);
|
||||
|
||||
MDEBUG("Sending " << self.outgoing.front().body.size() << " bytes in HTTP " << (self.outgoing.front().is_get() ? "GET" : "POST") << " to " << self.outgoing.front());
|
||||
BOOST_ASIO_CORO_YIELD self.async_write(std::move(*this));
|
||||
|
||||
if (!error)
|
||||
{
|
||||
MDEBUG("Starting read from " << self.outgoing.front() << " to previous HTTP message");
|
||||
BOOST_ASIO_CORO_YIELD self.async_read(std::move(*this));
|
||||
|
||||
if (error)
|
||||
MERROR("Failed to parse HTTP response from " << self.outgoing.front() << ": " << error.message());
|
||||
else if (self.parser->get().result_int() != 200 && self.parser->get().result_int() != 201)
|
||||
{
|
||||
const auto result = self.parser->get().result_int();
|
||||
MERROR(self.outgoing.front() << " returned " << result << " status code");
|
||||
if (0 < result && result <= std::numeric_limits<int>::max())
|
||||
self.outgoing.front().notifier(http::error(result), epee::byte_slice{});
|
||||
else
|
||||
self.outgoing.front().notifier(http::error::invalid_code, epee::byte_slice{});
|
||||
}
|
||||
else
|
||||
{
|
||||
MDEBUG(self.outgoing.front() << " successful");
|
||||
if (self.outgoing.front().notifier)
|
||||
self.outgoing.front().notifier({}, epee::byte_slice{std::move(self.parser->get()).body()});
|
||||
}
|
||||
}
|
||||
else
|
||||
MERROR("Failed HTTP " << (self.outgoing.front().is_get() ? "GET" : "POST") << " to " << self.outgoing.front() << ": " << error.message());
|
||||
|
||||
// if write, read, or parse errors
|
||||
if (error)
|
||||
return self.notify_connection_error();
|
||||
|
||||
self.outgoing.pop_front();
|
||||
++self.iteration;
|
||||
|
||||
if (!self.parser->get().keep_alive())
|
||||
{
|
||||
self.close();
|
||||
if (!self.outgoing.empty())
|
||||
return client_loop{self_}();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
void client::queue_async(std::string&& target, epee::byte_slice&& body, std::function<server_response_func>&& notifier, boost::beast::http::verb verb) const
|
||||
{
|
||||
message msg{std::move(body), std::move(target), std::move(notifier), verb};
|
||||
std::shared_ptr<client_state> state;
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{sync_};
|
||||
state = state_;
|
||||
}
|
||||
|
||||
if (!state)
|
||||
return msg.notifier(common_error::kInvalidArgument, epee::byte_slice{});
|
||||
|
||||
MDEBUG("Queueing HTTP " << (msg.is_get() ? "GET" : "POST") << " to " << msg << " using " << this);
|
||||
boost::asio::dispatch(
|
||||
state->strand,
|
||||
[state, msg = std::move(msg)] () mutable
|
||||
{
|
||||
const bool empty = state->outgoing.empty();
|
||||
state->outgoing.push_back(std::move(msg));
|
||||
if (empty)
|
||||
boost::asio::post(state->strand, client_loop{state});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void client::direct::operator()(std::shared_ptr<client_state> self, std::function<callback_func> f) const
|
||||
{
|
||||
struct frame
|
||||
{
|
||||
std::function<callback_func> f;
|
||||
boost::asio::ip::tcp::resolver resolver;
|
||||
const std::size_t iteration;
|
||||
|
||||
frame(client_state& self, std::function<callback_func>&& f)
|
||||
: f(std::move(f)), resolver(self.strand.context()), iteration(self.iteration)
|
||||
{}
|
||||
};
|
||||
|
||||
struct handler : boost::asio::coroutine
|
||||
{
|
||||
std::shared_ptr<client_state> self_;
|
||||
std::shared_ptr<frame> frame_;
|
||||
|
||||
explicit handler(std::shared_ptr<client_state> self, std::shared_ptr<frame>&& in)
|
||||
: boost::asio::coroutine(), self_(std::move(self)), frame_(std::move(in))
|
||||
{}
|
||||
|
||||
void operator()(boost::system::error_code error = {})
|
||||
{
|
||||
LWSF_VERIFY(self_ && frame_);
|
||||
|
||||
client_state& self = *self_;
|
||||
assert(self.strand.running_in_this_thread());
|
||||
BOOST_ASIO_CORO_REENTER(*this)
|
||||
{
|
||||
struct resolve
|
||||
{
|
||||
handler continue_;
|
||||
|
||||
void operator()(const boost::system::error_code error, const boost::asio::ip::tcp::resolver::results_type& ips)
|
||||
{
|
||||
if (error)
|
||||
std::move(continue_)(error);
|
||||
else if (ips.empty())
|
||||
std::move(continue_)(boost::asio::error::host_not_found);
|
||||
else if (continue_.self_)
|
||||
{
|
||||
continue_.self_->endpoint = *ips.begin();
|
||||
std::move(continue_)(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct on_timeout
|
||||
{
|
||||
std::shared_ptr<client_state> self_;
|
||||
std::shared_ptr<frame> frame_;
|
||||
void operator()(boost::system::error_code error) const
|
||||
{
|
||||
LWSF_VERIFY(frame_ && self_);
|
||||
assert(self_->strand.running_in_this_thread());
|
||||
|
||||
frame_->resolver.cancel();
|
||||
self_->close();
|
||||
frame_->f(boost::asio::error::operation_aborted);
|
||||
}
|
||||
};
|
||||
|
||||
set_timeout(self_, config::connect_timeout, on_timeout{self_, frame_});
|
||||
|
||||
MDEBUG("Resolving " << self.host << " for HTTP");
|
||||
BOOST_ASIO_CORO_YIELD frame_->resolver.async_resolve(
|
||||
self.host, std::to_string(self.port), boost::asio::bind_executor(self.strand, resolve{*this})
|
||||
);
|
||||
|
||||
if (!error)
|
||||
{
|
||||
MDEBUG("Connecting to " << self.endpoint << " / " << self.host << " for HTTP");
|
||||
BOOST_ASIO_CORO_YIELD self.sock.next_layer().async_connect(
|
||||
self.endpoint, boost::asio::bind_executor(self.strand, std::move(*this))
|
||||
);
|
||||
|
||||
if (error)
|
||||
MERROR("Failed to connect to " << self.host << ": " << error.message());
|
||||
}
|
||||
else
|
||||
MERROR("Failed to resolve TCP/IP address for " << self.host << ": " << error.message());
|
||||
|
||||
if (self_->iteration <= frame_->iteration)
|
||||
frame_->f(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LWSF_VERIFY(self);
|
||||
handler{self, std::make_shared<frame>(*self, std::move(f))}();
|
||||
}
|
||||
|
||||
void client::socks::operator()(std::shared_ptr<client_state> self, std::function<callback_func> f) const
|
||||
{
|
||||
struct handler
|
||||
{
|
||||
std::shared_ptr<client_state> self_;
|
||||
std::function<callback_func> f_;
|
||||
|
||||
void operator()(boost::system::error_code error, boost::asio::ip::tcp::socket&& sock)
|
||||
{
|
||||
LWSF_VERIFY(self_ && f_);
|
||||
|
||||
if (error)
|
||||
MERROR("Failed socks connection: " << error.message());
|
||||
self_->sock.next_layer() = std::move(sock);
|
||||
|
||||
std::function<callback_func> f{std::move(f_)};
|
||||
boost::asio::dispatch(self_->strand, [f = std::move(f), error] { f(error); });
|
||||
};
|
||||
};
|
||||
|
||||
LWSF_VERIFY(self && f);
|
||||
|
||||
std::shared_ptr<::net::socks::client> proxy = ::net::socks::make_connect_client(
|
||||
std::move(self->sock.next_layer()), ::net::socks::version::v4a, handler{self, f}
|
||||
);
|
||||
|
||||
bool is_set = false;
|
||||
std::uint32_t ip_address = 0;
|
||||
if (epee::string_tools::get_ip_int32_from_string(ip_address, self->host))
|
||||
is_set = proxy->set_connect_command(epee::net_utils::ipv4_network_address{ip_address, self->port});
|
||||
else
|
||||
is_set = proxy->set_connect_command(self->host, self->port);
|
||||
|
||||
if (is_set)
|
||||
{
|
||||
set_timeout(self, config::connect_timeout, ::net::socks::client::async_close{proxy});
|
||||
is_set = ::net::socks::client::connect_and_send(std::move(proxy), proxy_address);
|
||||
}
|
||||
|
||||
if (!is_set)
|
||||
{
|
||||
MERROR("Failed to initiate socks proxy");
|
||||
f(boost::asio::error::fault);
|
||||
}
|
||||
}
|
||||
|
||||
void client::init(boost::asio::io_context& io, std::string host, std::string prefix, std::uint16_t port, epee::net_utils::ssl_options_t ssl)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{sync_};
|
||||
state_ = std::make_shared<client_state>(io, std::move(host), std::move(prefix), port, std::move(ssl), proxy_);
|
||||
}
|
||||
|
||||
client::~client()
|
||||
{}
|
||||
|
||||
bool client::is_connected() const noexcept
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{sync_};
|
||||
return state_ && state_->is_connected;
|
||||
}
|
||||
|
||||
void client::set_proxy(std::function<connect_func> connector)
|
||||
{
|
||||
LWSF_VERIFY(connector);
|
||||
const boost::lock_guard<boost::mutex> lock{sync_};
|
||||
proxy_ = std::move(connector);
|
||||
if (state_)
|
||||
state_ = std::make_shared<client_state>(state_->strand.context(), state_->host, state_->prefix, state_->port, state_->ssl, proxy_);
|
||||
}
|
||||
|
||||
void client::post_async(std::string url, epee::byte_slice json_body, std::function<server_response_func> notifier) const
|
||||
{
|
||||
return queue_async(std::move(url), std::move(json_body), std::move(notifier), boost::beast::http::verb::post);
|
||||
}
|
||||
|
||||
void client::get_async(std::string url, std::function<server_response_func> notifier) const
|
||||
{
|
||||
if (!notifier)
|
||||
throw std::logic_error{"net::http::client::get_async requires callback"};
|
||||
return queue_async(std::move(url), epee::byte_slice{}, std::move(notifier), boost::beast::http::verb::get);
|
||||
}
|
||||
}}} // lwsf // internal // http
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2024, 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 <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/beast/http/verb.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include "byte_slice.h" // monero/contrib/epee/include
|
||||
#include "net/net_ssl.h" // monero/contrib/epee/include
|
||||
|
||||
namespace lwsf { namespace internal { namespace http
|
||||
{
|
||||
enum class error : int
|
||||
{
|
||||
none = 0, timeout = -1, invalid_code = -2 /* Otherwise HTTP error code */
|
||||
};
|
||||
|
||||
const std::error_category& error_category() noexcept;
|
||||
inline std::error_code make_error_code(const error value) noexcept
|
||||
{
|
||||
return std::error_code{int(value), error_category()};
|
||||
}
|
||||
|
||||
struct client_state;
|
||||
using server_response_func = void(std::error_code, epee::byte_slice);
|
||||
using callback_func = void(boost::system::error_code);
|
||||
using connect_func = void(std::shared_ptr<client_state>, std::function<callback_func>);
|
||||
|
||||
//! Primarily for webhooks, where the response is (basically) ignored.
|
||||
class client
|
||||
{
|
||||
|
||||
std::shared_ptr<client_state> state_;
|
||||
std::function<connect_func> proxy_;
|
||||
mutable boost::mutex sync_;
|
||||
|
||||
void queue_async(std::string&& target, epee::byte_slice&& body, std::function<server_response_func>&& notifier, boost::beast::http::verb verb) const;
|
||||
|
||||
public:
|
||||
struct direct
|
||||
{
|
||||
void operator()(std::shared_ptr<client_state>, std::function<callback_func>) const;
|
||||
};
|
||||
|
||||
struct socks
|
||||
{
|
||||
boost::asio::ip::tcp::endpoint proxy_address;
|
||||
void operator()(std::shared_ptr<client_state>, std::function<callback_func>) const;
|
||||
};
|
||||
|
||||
client()
|
||||
: state_(nullptr), proxy_(direct{}), sync_()
|
||||
{}
|
||||
|
||||
void init(boost::asio::io_context& io, std::string host, std::string prefix, std::uint16_t port, epee::net_utils::ssl_options_t ssl);
|
||||
|
||||
client(client&&) = delete;
|
||||
client(const client&) = delete;
|
||||
~client();
|
||||
client& operator=(client&&) = delete;
|
||||
client& operator=(const client&) = delete;
|
||||
|
||||
//! thread-safe
|
||||
bool is_connected() const noexcept;
|
||||
|
||||
//! thread-safe
|
||||
void set_proxy(std::function<connect_func> connector);
|
||||
|
||||
//! Never blocks. Thread safe. \return `success()` if `url` is valid.
|
||||
void post_async(std::string target, epee::byte_slice body, std::function<server_response_func> notifier) const;
|
||||
|
||||
/*! Never blocks. Thread safe. Calls `notifier` with server response iff
|
||||
`success()` is returned.
|
||||
\return `success()` if `url` is valid. */
|
||||
void get_async(std::string target, std::function<server_response_func> notifier) const;
|
||||
};
|
||||
}}} // lwsf // internal // http
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
struct is_error_code_enum<lwsf::internal::http::error>
|
||||
: true_type
|
||||
{};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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 "wallet_io.h"
|
||||
|
||||
#include "backend.h"
|
||||
#include "net/context.h"
|
||||
|
||||
namespace lwsf { namespace internal { namespace net
|
||||
{
|
||||
wallet_io::wallet_io()
|
||||
: context(std::make_shared<net::context>()),
|
||||
wallet(std::make_shared<backend::wallet>(context->io_))
|
||||
{}
|
||||
}}} // lwsf // internal // net
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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 <memory>
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
namespace backend { struct wallet; }
|
||||
namespace net
|
||||
{
|
||||
struct context;
|
||||
|
||||
/*! \brief Pass together because `wallet` has references to `context`.
|
||||
|
||||
ASIO routines only take the `wallet` object, otherwise cyclic references
|
||||
can occur during destruction (when I/O is pending at time of destruction).
|
||||
Additionally, `context` must remain active until `wallet` is destructed, or
|
||||
ASIO functions have a dangling reference. Pass this object when `wallet` is
|
||||
needed, and provide `wallet` to the `static` methods of `backend::wallet`
|
||||
that perform ASIO routines. */
|
||||
struct wallet_io
|
||||
{
|
||||
std::shared_ptr<net::context> context; //<! Must be first (destruct last)
|
||||
std::shared_ptr<backend::wallet> wallet; //!< Has references to `context`
|
||||
|
||||
wallet_io();
|
||||
};
|
||||
} // net
|
||||
}} // lwsf // intenral
|
||||
+36
-27
@@ -26,16 +26,17 @@
|
||||
// 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 "backend.h"
|
||||
#include "pending_transaction.h"
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/thread/lock_guard.hpp>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include "backend.h"
|
||||
#include "byte_slice.h" // monero/contrib/epee/include
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src
|
||||
#include "error.h"
|
||||
#include "net/context.h"
|
||||
#include "numeric.h"
|
||||
#include "string_tools.h" // monero/contrib/epee/include
|
||||
#include "utils/encrypted_file.h"
|
||||
@@ -100,40 +101,37 @@ namespace lwsf { namespace internal
|
||||
}
|
||||
|
||||
|
||||
pending_transaction::pending_transaction(std::shared_ptr<backend::wallet> wallet, std::string error, std::vector<std::shared_ptr<backend::transaction>> local)
|
||||
: wallet_(std::move(wallet)), local_(std::move(local)), error_(std::move(error))
|
||||
pending_transaction::pending_transaction(const net::wallet_io& data, std::string error, std::vector<std::shared_ptr<backend::transaction>> local)
|
||||
: data_(data), local_(std::move(local)), error_(std::move(error))
|
||||
{
|
||||
if (!wallet_)
|
||||
throw std::invalid_argument{"lwsf::internal::pending_transaction cannot be given nullptr backend"};
|
||||
LWSF_VERIFY(data_.context && data_.wallet);
|
||||
|
||||
for (const auto& e : local_)
|
||||
{
|
||||
if (!e)
|
||||
throw std::invalid_argument{"lwsf::internal::pending_transaction cannot be given nullptr backend::transaction"};
|
||||
LWSF_VERIFY(bool(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<pending_transaction> pending_transaction::load_from_file(std::shared_ptr<backend::wallet> wallet, const std::string& filename)
|
||||
std::unique_ptr<pending_transaction> pending_transaction::load_from_file(const net::wallet_io& data, const std::string& filename)
|
||||
{
|
||||
if (!wallet)
|
||||
throw std::invalid_argument{"lwsf::internal::pending_transaction::load_from_file cannot be given nullptr backend"};
|
||||
LWSF_VERIFY(data.wallet);
|
||||
|
||||
epee::byte_slice file = try_load(filename, tx_file_magic);
|
||||
if (file.empty())
|
||||
return std::make_unique<pending_transaction>(wallet, "Invalid path/file for tx");
|
||||
return std::make_unique<pending_transaction>(data, "Invalid path/file for tx");
|
||||
|
||||
expect<epee::byte_slice> payload = epee::byte_slice{};
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{wallet->sync};
|
||||
payload = decrypt(std::move(file), epee::as_byte_span(unwrap(unwrap(wallet->primary.view.sec))));
|
||||
const boost::lock_guard<boost::mutex> lock{data.wallet->sync};
|
||||
payload = decrypt(std::move(file), epee::as_byte_span(unwrap(unwrap(data.wallet->primary.view.sec))));
|
||||
}
|
||||
if (!payload)
|
||||
return std::make_unique<pending_transaction>(wallet, payload.error().message());
|
||||
return std::make_unique<pending_transaction>(data, payload.error().message());
|
||||
|
||||
txes_file dest{};
|
||||
if (std::error_code error = wire::msgpack::from_bytes(std::move(*payload), dest))
|
||||
return std::make_unique<pending_transaction>(wallet, error.message());
|
||||
return std::make_unique<pending_transaction>(wallet, "", std::move(dest.txes));
|
||||
return std::make_unique<pending_transaction>(data, error.message());
|
||||
return std::make_unique<pending_transaction>(data, "", std::move(dest.txes));
|
||||
}
|
||||
|
||||
pending_transaction::~pending_transaction()
|
||||
@@ -144,18 +142,29 @@ namespace lwsf { namespace internal
|
||||
if (!error_.empty())
|
||||
return false;
|
||||
|
||||
for (auto e : local_)
|
||||
try
|
||||
{
|
||||
const std::error_code error = wallet_->send_tx(e->raw_bytes.clone());
|
||||
if (error)
|
||||
for (auto e : local_)
|
||||
{
|
||||
error_ = error.message();
|
||||
return false;
|
||||
const auto error = data_.context->wait_for<std::error_code>(
|
||||
[this, &e] (auto&& f)
|
||||
{ backend::wallet::send_tx(this->data_.wallet, e->raw_bytes.clone(), std::move(f)); }
|
||||
);
|
||||
if (error)
|
||||
{
|
||||
error_ = error.message();
|
||||
return false;
|
||||
}
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
auto entry = data_.wallet->primary.txes.try_emplace(e->id).first;
|
||||
if (!entry->second)
|
||||
entry->second = std::move(e);
|
||||
}
|
||||
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
|
||||
auto entry = wallet_->primary.txes.try_emplace(e->id).first;
|
||||
if (!entry->second)
|
||||
entry->second = std::move(e);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
error_ = e.what();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -189,12 +198,12 @@ namespace lwsf { namespace internal
|
||||
return false;
|
||||
}
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
bytes = encrypt(
|
||||
tx_file_magic,
|
||||
epee::byte_slice{std::move(sink)},
|
||||
0 /* iterations */,
|
||||
epee::as_byte_span(unwrap(unwrap(wallet_->primary.view.sec)))
|
||||
epee::as_byte_span(unwrap(unwrap(data_.wallet->primary.view.sec)))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,26 +36,23 @@
|
||||
#include <vector>
|
||||
#include "common/expect.h" // monero/src
|
||||
#include "cryptonote_basic/cryptonote_basic.h" // monero/src
|
||||
#include "net/wallet_io.h"
|
||||
#include "wallet/api/wallet2_api.h" // moneor/src
|
||||
#include "wire/fwd.h"
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
namespace backend
|
||||
{
|
||||
struct transaction;
|
||||
struct wallet;
|
||||
}
|
||||
namespace backend { struct transaction; }
|
||||
|
||||
class pending_transaction final : public Monero::PendingTransaction
|
||||
{
|
||||
const std::shared_ptr<backend::wallet> wallet_;
|
||||
const net::wallet_io data_;
|
||||
const std::vector<std::shared_ptr<backend::transaction>> local_;
|
||||
std::string error_;
|
||||
|
||||
public:
|
||||
pending_transaction(std::shared_ptr<backend::wallet> wallet, std::string error, std::vector<std::shared_ptr<backend::transaction>> local = {});
|
||||
static std::unique_ptr<pending_transaction> load_from_file(std::shared_ptr<backend::wallet> wallet, const std::string& filename);
|
||||
pending_transaction(const net::wallet_io& data, std::string error, std::vector<std::shared_ptr<backend::transaction>> local = {});
|
||||
static std::unique_ptr<pending_transaction> load_from_file(const net::wallet_io& data, const std::string& filename);
|
||||
|
||||
pending_transaction(pending_transaction&&) = delete;
|
||||
pending_transaction(const pending_transaction&) = delete;
|
||||
|
||||
+12
-12
@@ -33,6 +33,7 @@
|
||||
#include <utility>
|
||||
#include "backend.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src
|
||||
#include "error.h"
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
@@ -66,11 +67,10 @@ namespace lwsf { namespace internal
|
||||
}
|
||||
}
|
||||
|
||||
subaddress_account::subaddress_account(internal::wallet* wal, std::shared_ptr<backend::wallet> data)
|
||||
: wal_(wal), data_(std::move(data)), rows_()
|
||||
subaddress_account::subaddress_account(internal::wallet* wal, const net::wallet_io& data)
|
||||
: wal_(wal), data_(data), rows_()
|
||||
{
|
||||
if (!wal_ || !data_)
|
||||
throw std::invalid_argument{"lwsf::internal::subaddress_account cannot be given nullptr"};
|
||||
LWSF_VERIFY(wal_ && data_.wallet);
|
||||
}
|
||||
|
||||
subaddress_account::~subaddress_account()
|
||||
@@ -88,8 +88,8 @@ namespace lwsf { namespace internal
|
||||
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).detail.try_emplace(0).first->second.label = label;
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
data_.wallet->primary.subaccounts.at(accountIndex).detail.try_emplace(0).first->second.label = label;
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
@@ -101,11 +101,11 @@ namespace lwsf { namespace internal
|
||||
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;
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
const Monero::NetworkType net_type = data_.wallet->primary.type;
|
||||
const std::uint32_t chain_height = data_.wallet->blockchain_height;
|
||||
|
||||
for (const auto& tx : data_->primary.txes)
|
||||
for (const auto& tx : data_.wallet->primary.txes)
|
||||
{
|
||||
if (tx.second->failed)
|
||||
continue;
|
||||
@@ -130,7 +130,7 @@ namespace lwsf { namespace internal
|
||||
static constexpr const std::uint32_t max_index =
|
||||
std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
const auto& accts = data_->primary.subaccounts;
|
||||
const auto& accts = data_.wallet->primary.subaccounts;
|
||||
rows_.reserve(accts.size());
|
||||
for (std::size_t i = 0; i < accts.size() && i <= max_index; ++i)
|
||||
{
|
||||
@@ -138,7 +138,7 @@ namespace lwsf { namespace internal
|
||||
rows_.push_back(
|
||||
new Monero::SubaddressAccountRow{
|
||||
std::uint32_t(i),
|
||||
data_->get_spend_address({std::uint32_t(i), 0}),
|
||||
data_.wallet->get_spend_address({std::uint32_t(i), 0}),
|
||||
std::string{accts.at(i).primary_label()},
|
||||
cryptonote::print_money(balance.total),
|
||||
cryptonote::print_money(balance.unlocked)
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "net/wallet_io.h"
|
||||
#include "wallet.h"
|
||||
#include "wallet/api/wallet2_api.h" // monero/src
|
||||
|
||||
@@ -41,12 +42,12 @@ namespace lwsf { namespace internal
|
||||
class subaddress_account final : public ::Monero::SubaddressAccount
|
||||
{
|
||||
internal::wallet* const wal_;
|
||||
const std::shared_ptr<backend::wallet> data_;
|
||||
const net::wallet_io data_;
|
||||
std::vector<Monero::SubaddressAccountRow*> rows_;
|
||||
|
||||
public:
|
||||
|
||||
explicit subaddress_account(internal::wallet* wal, std::shared_ptr<backend::wallet> data);
|
||||
explicit subaddress_account(internal::wallet* wal, const net::wallet_io& data);
|
||||
|
||||
subaddress_account(const subaddress_account&) = delete;
|
||||
subaddress_account(subaddress_account&&) = delete;
|
||||
|
||||
+10
-10
@@ -33,6 +33,7 @@
|
||||
#include <utility>
|
||||
#include "backend.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h" // monero/src
|
||||
#include "error.h"
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
@@ -46,11 +47,10 @@ namespace lwsf { namespace internal
|
||||
}
|
||||
}
|
||||
|
||||
subaddress_minor::subaddress_minor(internal::wallet* wal, std::shared_ptr<backend::wallet> data)
|
||||
: wal_(wal), data_(std::move(data)), rows_()
|
||||
subaddress_minor::subaddress_minor(internal::wallet* wal, const net::wallet_io& data)
|
||||
: wal_(wal), data_(data), rows_()
|
||||
{
|
||||
if (!wal_ || !data_)
|
||||
throw std::invalid_argument{"lwsf::internal::subaddress_minor cannot be given nullptr"};
|
||||
LWSF_VERIFY(wal_ && data_.wallet);
|
||||
}
|
||||
|
||||
subaddress_minor::~subaddress_minor()
|
||||
@@ -64,11 +64,11 @@ namespace lwsf { namespace internal
|
||||
{
|
||||
free_rows(rows_);
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
if (data_->primary.subaccounts.size() <= accountIndex)
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
if (data_.wallet->primary.subaccounts.size() <= accountIndex)
|
||||
return;
|
||||
|
||||
const auto& acct = data_->primary.subaccounts[accountIndex];
|
||||
const auto& acct = data_.wallet->primary.subaccounts[accountIndex];
|
||||
static_assert(std::is_same<std::uint32_t, decltype(acct.last)>());
|
||||
rows_.reserve(std::size_t(acct.last) + 1);
|
||||
for (std::uint64_t i = 0; i <= acct.last; ++i)
|
||||
@@ -76,7 +76,7 @@ namespace lwsf { namespace internal
|
||||
rows_.push_back(
|
||||
new Monero::SubaddressRow{
|
||||
std::uint32_t(i),
|
||||
data_->get_spend_address({accountIndex, std::uint32_t(i)}),
|
||||
data_.wallet->get_spend_address({accountIndex, std::uint32_t(i)}),
|
||||
std::string{acct.sub_label(i)}
|
||||
}
|
||||
);
|
||||
@@ -99,8 +99,8 @@ namespace lwsf { namespace internal
|
||||
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};
|
||||
auto& acct = data_->primary.subaccounts.at(accountIndex);
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
auto& acct = data_.wallet->primary.subaccounts.at(accountIndex);
|
||||
if (!addressIndex || !label.empty())
|
||||
acct.detail.try_emplace(addressIndex).first->second.label = label;
|
||||
else
|
||||
|
||||
@@ -31,22 +31,21 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "net/wallet_io.h"
|
||||
#include "wallet.h"
|
||||
#include "wallet/api/wallet2_api.h" // monero/src
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
namespace backend { struct wallet; }
|
||||
|
||||
class subaddress_minor final : public ::Monero::Subaddress
|
||||
{
|
||||
internal::wallet* const wal_;
|
||||
const std::shared_ptr<backend::wallet> data_;
|
||||
const net::wallet_io data_;
|
||||
std::vector<Monero::SubaddressRow*> rows_;
|
||||
|
||||
public:
|
||||
|
||||
explicit subaddress_minor(internal::wallet* wal, std::shared_ptr<backend::wallet> data);
|
||||
explicit subaddress_minor(internal::wallet* wal, const net::wallet_io& data);
|
||||
|
||||
subaddress_minor(const subaddress_minor&) = delete;
|
||||
subaddress_minor(subaddress_minor&&) = delete;
|
||||
|
||||
+16
-16
@@ -34,9 +34,10 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include "backend.h"
|
||||
#include "error.h"
|
||||
#include "hex.h" // monero/contrib/epee/include
|
||||
#include "transaction_info.h"
|
||||
|
||||
#include "hex.h" // monero/contrib/epee/include
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
{
|
||||
@@ -50,11 +51,10 @@ namespace lwsf { namespace internal
|
||||
}
|
||||
}
|
||||
|
||||
transaction_history::transaction_history(std::shared_ptr<backend::wallet> data)
|
||||
: data_(std::move(data)), txes_(), by_id_()
|
||||
transaction_history::transaction_history(const net::wallet_io& data)
|
||||
: data_(data), txes_(), by_id_()
|
||||
{
|
||||
if (!data_)
|
||||
throw std::invalid_argument{"lwsf::internal::transaction_history cannot be given nullptr"};
|
||||
LWSF_VERIFY(data_.wallet);
|
||||
}
|
||||
|
||||
transaction_history::~transaction_history()
|
||||
@@ -76,12 +76,12 @@ namespace lwsf { namespace internal
|
||||
return nullptr;
|
||||
|
||||
// hold the lock during the map lookup, this is const and should be thread-safe
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
auto& info = by_id_[binary_id];
|
||||
if (!info)
|
||||
{
|
||||
const auto iter = data_->primary.txes.find(binary_id);
|
||||
if (iter == data_->primary.txes.end())
|
||||
const auto iter = data_.wallet->primary.txes.find(binary_id);
|
||||
if (iter == data_.wallet->primary.txes.end())
|
||||
return nullptr;
|
||||
info = std::make_unique<transaction_info>(data_, iter->second);
|
||||
}
|
||||
@@ -92,24 +92,24 @@ namespace lwsf { namespace internal
|
||||
{
|
||||
by_id_.clear();
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
if (std::numeric_limits<int>::max() < data_->primary.txes.size())
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
if (std::numeric_limits<int>::max() < data_.wallet->primary.txes.size())
|
||||
throw std::runtime_error{"lwsf::internal::transaction_history::refresh exceeds max history size"};
|
||||
|
||||
// be careful about exceptions and memory leaks. this should be safe
|
||||
|
||||
for (std::size_t i = data_->primary.txes.size(); i < txes_.size(); ++i)
|
||||
for (std::size_t i = data_.wallet->primary.txes.size(); i < txes_.size(); ++i)
|
||||
{
|
||||
const std::unique_ptr<Monero::TransactionInfo> destroy{txes_[i]};
|
||||
txes_[i] = nullptr;
|
||||
}
|
||||
|
||||
txes_.resize(data_->primary.txes.size());
|
||||
txes_.resize(data_.wallet->primary.txes.size());
|
||||
|
||||
try
|
||||
{
|
||||
std::size_t i = 0;
|
||||
for (const auto& backend_tx : data_->primary.txes)
|
||||
for (const auto& backend_tx : data_.wallet->primary.txes)
|
||||
{
|
||||
if (txes_[i])
|
||||
static_cast<transaction_info*>(txes_[i])->update(backend_tx.second);
|
||||
@@ -131,9 +131,9 @@ namespace lwsf { namespace internal
|
||||
if (!epee::from_hex::to_buffer(epee::as_mut_byte_span(binary_id), txid))
|
||||
return;
|
||||
|
||||
const boost::lock_guard<boost::mutex> lock{data_->sync};
|
||||
auto iter = data_->primary.txes.find(binary_id);
|
||||
if (iter != data_->primary.txes.end())
|
||||
const boost::lock_guard<boost::mutex> lock{data_.wallet->sync};
|
||||
auto iter = data_.wallet->primary.txes.find(binary_id);
|
||||
if (iter != data_.wallet->primary.txes.end())
|
||||
iter->second->description = note;
|
||||
}
|
||||
}} // lwsf // internal
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "crypto/hash.h" // monero/src
|
||||
#include "net/wallet_io.h"
|
||||
#include "wallet/api/wallet2_api.h" // monero/src
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
@@ -39,12 +40,12 @@ namespace lwsf { namespace internal
|
||||
namespace backend { class wallet; }
|
||||
class transaction_history final : public Monero::TransactionHistory
|
||||
{
|
||||
const std::shared_ptr<backend::wallet> data_;
|
||||
const net::wallet_io data_;
|
||||
std::vector<Monero::TransactionInfo*> txes_;
|
||||
mutable std::unordered_map<crypto::hash, std::unique_ptr<Monero::TransactionInfo>> by_id_;
|
||||
|
||||
public:
|
||||
explicit transaction_history(std::shared_ptr<backend::wallet> data);
|
||||
explicit transaction_history(const net::wallet_io& data);
|
||||
|
||||
transaction_history(const transaction_history&) = delete;
|
||||
transaction_history(transaction_history&&) = delete;
|
||||
|
||||
+11
-12
@@ -31,6 +31,7 @@
|
||||
#include <boost/container/throw_exception.hpp>
|
||||
#include <boost/thread/lock_guard.hpp>
|
||||
#include <boost/version.hpp>
|
||||
#include "error.h"
|
||||
#include "hex.h"
|
||||
|
||||
namespace
|
||||
@@ -57,11 +58,10 @@ namespace lwsf { namespace internal
|
||||
transfers_.emplace_back(out.amount, out.address);
|
||||
}
|
||||
|
||||
transaction_info::transaction_info(std::shared_ptr<backend::wallet> wallet, std::shared_ptr<const backend::transaction> data)
|
||||
: wallet_(std::move(wallet)), data_(std::move(data)), transfers_()
|
||||
transaction_info::transaction_info(const net::wallet_io& io, std::shared_ptr<const backend::transaction> data)
|
||||
: io_(io), data_(std::move(data)), transfers_()
|
||||
{
|
||||
if (!wallet_ || !data_)
|
||||
throw std::invalid_argument{"lwsf::internal::transaction_info cannot be given nullptr"};
|
||||
LWSF_VERIFY(io_.wallet && data_);
|
||||
update_transfers();
|
||||
}
|
||||
|
||||
@@ -70,8 +70,7 @@ namespace lwsf { namespace internal
|
||||
|
||||
void transaction_info::update(std::shared_ptr<const backend::transaction> data)
|
||||
{
|
||||
if (!data)
|
||||
throw std::invalid_argument{"lwsf::internal::transaction_info::update cannot be given nullptr"};
|
||||
LWSF_VERIFY(data);
|
||||
data_ = std::move(data);
|
||||
update_transfers();
|
||||
}
|
||||
@@ -87,7 +86,7 @@ namespace lwsf { namespace internal
|
||||
|
||||
std::string transaction_info::description() const
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
|
||||
const boost::lock_guard<boost::mutex> lock{io_.wallet->sync};
|
||||
return data_->description; // this can change outside of refresh func
|
||||
}
|
||||
|
||||
@@ -126,10 +125,10 @@ namespace lwsf { namespace internal
|
||||
{
|
||||
const auto get_label = [this] (const rpc::address_meta& meta) -> std::string
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
|
||||
if (meta.maj_i < wallet_->primary.subaccounts.size())
|
||||
const boost::lock_guard<boost::mutex> lock{io_.wallet->sync};
|
||||
if (meta.maj_i < io_.wallet->primary.subaccounts.size())
|
||||
{
|
||||
const auto& major = wallet_->primary.subaccounts.at(meta.maj_i);
|
||||
const auto& major = io_.wallet->primary.subaccounts.at(meta.maj_i);
|
||||
const auto minor = major.detail.find(meta.min_i);
|
||||
if (minor != major.detail.end())
|
||||
return minor->second.label;
|
||||
@@ -152,8 +151,8 @@ namespace lwsf { namespace internal
|
||||
{
|
||||
if (!data_->height)
|
||||
return 0;
|
||||
const boost::lock_guard<boost::mutex> lock{wallet_->sync};
|
||||
return std::max(wallet_->blockchain_height, *data_->height) - *data_->height + 1;
|
||||
const boost::lock_guard<boost::mutex> lock{io_.wallet->sync};
|
||||
return std::max(io_.wallet->blockchain_height, *data_->height) - *data_->height + 1;
|
||||
}
|
||||
|
||||
std::string transaction_info::hash() const
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "backend.h"
|
||||
#include "net/wallet_io.h"
|
||||
#include "wallet/api/wallet2_api.h" // monero/src
|
||||
|
||||
namespace lwsf { namespace internal
|
||||
@@ -40,7 +41,7 @@ namespace lwsf { namespace internal
|
||||
//! \todo isFailed, subaddressIndex, and subaddrAccount
|
||||
class transaction_info final : public Monero::TransactionInfo
|
||||
{
|
||||
const std::shared_ptr<backend::wallet> wallet_;
|
||||
const net::wallet_io io_;
|
||||
std::shared_ptr<const backend::transaction> data_;
|
||||
std::vector<Transfer> transfers_;
|
||||
|
||||
@@ -48,7 +49,7 @@ namespace lwsf { namespace internal
|
||||
|
||||
public:
|
||||
|
||||
explicit transaction_info(std::shared_ptr<backend::wallet> wallet, std::shared_ptr<const backend::transaction> data);
|
||||
explicit transaction_info(const net::wallet_io& io, std::shared_ptr<const backend::transaction> data);
|
||||
|
||||
transaction_info(const transaction_info&) = delete;
|
||||
transaction_info(transaction_info&&) = delete;
|
||||
|
||||
+379
-297
File diff suppressed because it is too large
Load Diff
+26
-11
@@ -42,19 +42,39 @@
|
||||
#include <system_error>
|
||||
#include "byte_slice.h" // monero/contrib/epee/include
|
||||
#include "crypto/crypto.h" // monero/src
|
||||
#include "net/wallet_io.h"
|
||||
#include "wallet/api/wallet2_api.h" // monero/src
|
||||
|
||||
namespace lwsf
|
||||
{
|
||||
namespace internal
|
||||
{
|
||||
namespace backend { struct wallet; }
|
||||
|
||||
class wallet final : public Monero::Wallet
|
||||
{
|
||||
enum class state : std::uint8_t { stop = 0, paused, skip_once, run };
|
||||
|
||||
const std::shared_ptr<backend::wallet> data_;
|
||||
struct frame
|
||||
{
|
||||
std::string exception_error_;
|
||||
std::error_code error_;
|
||||
std::chrono::milliseconds refresh_interval_;
|
||||
boost::mutex sync_;
|
||||
state thread_state_;
|
||||
|
||||
frame();
|
||||
|
||||
bool set_error(std::error_code error, bool clear);
|
||||
struct async_error
|
||||
{
|
||||
std::shared_ptr<frame> self;
|
||||
void operator()(std::error_code error) const;
|
||||
};
|
||||
|
||||
bool thead_start();
|
||||
};
|
||||
|
||||
const net::wallet_io data_;
|
||||
const std::shared_ptr<frame> status_;
|
||||
std::unique_ptr<Monero::AddressBook> addressbook_;
|
||||
std::unique_ptr<Monero::TransactionHistory> history_;
|
||||
std::unique_ptr<Monero::SubaddressAccount> subaddresses_;
|
||||
@@ -63,26 +83,21 @@ namespace internal
|
||||
std::string password_;
|
||||
std::string language_;
|
||||
std::string ca_file_path_;
|
||||
std::deque<std::function<std::error_code()>> work_queue_;
|
||||
mutable std::string exception_error_;
|
||||
mutable std::error_code error_;
|
||||
boost::thread thread_;
|
||||
const std::uint64_t iterations_;
|
||||
std::chrono::milliseconds refresh_interval_;
|
||||
std::uint32_t mixin_;
|
||||
boost::condition_variable refresh_notify_;
|
||||
mutable boost::mutex error_sync_;
|
||||
boost::mutex refresh_sync_; //!< Synchronizes with requests from other threads
|
||||
boost::mutex thread_sync_; //!< Syncrhonizes thread destruction+creation
|
||||
state thread_state_;
|
||||
bool mandatory_refresh_;
|
||||
|
||||
bool set_error(std::error_code status, bool clear = false) const;
|
||||
void set_critical(const std::exception& e) const;
|
||||
|
||||
template<typename F>
|
||||
void queue_work(F&& f);
|
||||
template<typename T, typename F>
|
||||
T wait_for(F&& f) const;
|
||||
|
||||
void check_worker_thread();
|
||||
void stop_refresh_loop();
|
||||
void refresh_loop();
|
||||
|
||||
|
||||
+40
-16
@@ -44,6 +44,7 @@
|
||||
#ifdef LWSF_POLYSEED_ENABLE
|
||||
#include "polyseed.h"
|
||||
#endif
|
||||
#include "net/context.h"
|
||||
#include "net/parse.h" // monero/src
|
||||
#include "net/socks_connect.h" // monero/src
|
||||
#include "QrCode.hpp" // monero/src/external
|
||||
@@ -57,27 +58,51 @@ namespace lwsf
|
||||
//! \TODO Mark final when completely implemented
|
||||
class wallet_manager : public Monero::WalletManager
|
||||
{
|
||||
rpc::http_client client_;
|
||||
std::string client_prefix_;
|
||||
boost::asio::io_context io_;
|
||||
http::client client_;
|
||||
std::string error_;
|
||||
rpc::daemon_status cached_;
|
||||
std::chrono::steady_clock::time_point cached_last_;
|
||||
boost::mutex sync_;
|
||||
|
||||
template<typename T, typename F>
|
||||
T wait_for(F f)
|
||||
{
|
||||
const boost::lock_guard<boost::mutex> lock{sync_};
|
||||
|
||||
net::to_future<T> callable{};
|
||||
auto value = callable.inner_->promise.get_future();
|
||||
f(std::move(callable));
|
||||
for (;;)
|
||||
{
|
||||
switch (value.wait_for(std::chrono::seconds{0}))
|
||||
{
|
||||
case std::future_status::deferred:
|
||||
case std::future_status::ready:
|
||||
return value.get();
|
||||
|
||||
case std::future_status::timeout:
|
||||
io_.restart();
|
||||
io_.run_one();
|
||||
break;
|
||||
};
|
||||
}
|
||||
throw std::logic_error{"unreachable"};
|
||||
}
|
||||
|
||||
rpc::daemon_status get_daemon_status()
|
||||
{
|
||||
if (std::chrono::steady_clock::now() - cached_last_ > config::daemon_status_cache)
|
||||
{
|
||||
const auto resp = rpc::invoke<rpc::daemon_status>(client_, client_prefix_, rpc::empty{});
|
||||
if (!resp)
|
||||
{
|
||||
error_ = resp.error().message();
|
||||
cached_ = rpc::daemon_status{};
|
||||
}
|
||||
rpc::daemon_status out{};
|
||||
const auto error = wait_for<std::error_code>(
|
||||
[this, &out] (auto&& f) { rpc::invoke_async(client_, rpc::empty{}, std::addressof(out), std::move(f)); }
|
||||
);
|
||||
if (error)
|
||||
error_ = error.message();
|
||||
else
|
||||
{
|
||||
error_.clear();
|
||||
cached_ = std::move(*resp);
|
||||
}
|
||||
cached_ = std::move(out);
|
||||
}
|
||||
return cached_;
|
||||
}
|
||||
@@ -85,7 +110,7 @@ namespace lwsf
|
||||
public:
|
||||
|
||||
wallet_manager()
|
||||
: client_(), error_(), cached_{}, cached_last_(std::chrono::seconds{0})
|
||||
: io_(), client_(), error_(), cached_{}, cached_last_(std::chrono::seconds{0}), sync_()
|
||||
{}
|
||||
|
||||
virtual ~wallet_manager() /* override */
|
||||
@@ -410,8 +435,7 @@ namespace lwsf
|
||||
url.port = 80;
|
||||
}
|
||||
|
||||
client_prefix_ = std::move(url.uri);
|
||||
client_.set_server(std::move(url.host), std::to_string(url.port), boost::none, std::move(options));
|
||||
client_.init(io_,std::move(url.host), std::move(url.uri), boost::numeric_cast<std::uint16_t>(url.port), std::move(options));
|
||||
}
|
||||
|
||||
//! returns whether the daemon can be reached, and its version number
|
||||
@@ -465,13 +489,13 @@ namespace lwsf
|
||||
|
||||
bool setProxy(const std::string &address) override
|
||||
{
|
||||
auto endpoint = net::get_tcp_endpoint(address);
|
||||
auto endpoint = ::net::get_tcp_endpoint(address);
|
||||
if (!endpoint)
|
||||
{
|
||||
error_ = endpoint.error().message();
|
||||
return false;
|
||||
}
|
||||
client_.set_connector(net::socks::connector{std::move(*endpoint)});
|
||||
client_.set_proxy(http::client::socks{std::move(*endpoint)});
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
add_library(lwsf-unit-framework framework.test.cpp)
|
||||
target_include_directories(lwsf-unit-framework PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_SOURCE_DIR}/src")
|
||||
target_include_directories(lwsf-unit-framework PUBLIC "${lwsf_SOURCE_DIR}/src" "${lwsf_SOURCE_DIR}/tests/unit")
|
||||
target_link_libraries(lwsf-unit-framework)
|
||||
|
||||
add_executable(lwsf-unit main.cpp backend.test.cpp rpc.test.cpp)
|
||||
|
||||
target_include_directories(lwsf-unit PRIVATE "${lwsf_SOURCE_DIR}/src")
|
||||
target_link_libraries(lwsf-unit PRIVATE lwsf-api lwsf-unit-framework monero::libraries)
|
||||
add_subdirectory(net)
|
||||
target_link_libraries(lwsf-unit lwsf-api lwsf-unit-framework lwsf-unit-net monero::libraries)
|
||||
add_test(NAME lwsf-unit COMMAND lwsf-unit -v)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
# 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.
|
||||
|
||||
add_library(lwsf-unit-net OBJECT http.test.cpp)
|
||||
#target_include_directories(lwsf-unit-net PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_SOURCE_DIR}/src")
|
||||
target_link_libraries(lwsf-unit-net PRIVATE lwsf-unit-framework lwsf-net monero::libraries)
|
||||
@@ -0,0 +1,232 @@
|
||||
// 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 "framework.test.h"
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/beast/core/flat_buffer.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/http/parser.hpp>
|
||||
#include <boost/beast/http/read.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/write.hpp>
|
||||
#include <string>
|
||||
#include "common/error.h" // monero/src
|
||||
#include "net/http.h"
|
||||
|
||||
using client_request =
|
||||
boost::beast::http::request<boost::beast::http::string_body>;
|
||||
|
||||
namespace
|
||||
{
|
||||
void get_next(boost::asio::io_context& io, boost::asio::ip::tcp::socket& server, boost::beast::flat_buffer& buffer, client_request& req)
|
||||
{
|
||||
bool ready = false;
|
||||
boost::beast::http::async_read(server, buffer, req, [&] (auto, auto) { ready = true; });
|
||||
|
||||
while (!ready)
|
||||
{
|
||||
io.restart();
|
||||
io.run_one();
|
||||
}
|
||||
}
|
||||
|
||||
epee::byte_slice to_slice(std::string src)
|
||||
{
|
||||
return epee::byte_slice{std::move(src)};
|
||||
}
|
||||
|
||||
std::string to_string(const epee::byte_slice& src)
|
||||
{
|
||||
return {reinterpret_cast<const char*>(src.data()), src.size()};
|
||||
}
|
||||
|
||||
void send(boost::asio::ip::tcp::socket& server, std::string body)
|
||||
{
|
||||
boost::beast::http::response<boost::beast::http::string_body> resp;
|
||||
resp.body() = std::move(body);
|
||||
resp.prepare_payload();
|
||||
boost::beast::http::write(server, resp);
|
||||
}
|
||||
}
|
||||
|
||||
LWS_CASE("net::http")
|
||||
{
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
|
||||
SETUP("client+server")
|
||||
{
|
||||
boost::asio::io_context io{};
|
||||
tcp::socket server{io};
|
||||
tcp::acceptor acceptor{io, tcp::endpoint(tcp::v4(), 0)};
|
||||
acceptor.listen();
|
||||
|
||||
boost::beast::flat_buffer buffer{};
|
||||
lwsf::internal::http::client client{};
|
||||
{
|
||||
std::error_code actual;
|
||||
client.get_async("/foo", [&] (std::error_code error, auto) { actual = error; });
|
||||
EXPECT(actual == common_error::kInvalidArgument);
|
||||
}
|
||||
{
|
||||
const auto endpoint = acceptor.local_endpoint();
|
||||
client.init(io, "127.0.0.1", "lws", endpoint.port(), epee::net_utils::ssl_support_t::e_ssl_support_disabled);
|
||||
|
||||
EXPECT(!client.is_connected());
|
||||
|
||||
bool client_ready = false;
|
||||
bool server_ready = false;
|
||||
client.get_async("/foo", [&] (auto, auto) { client_ready = true; });
|
||||
acceptor.async_accept(server, [&] (auto) { server_ready = true; });
|
||||
|
||||
EXPECT(!client.is_connected());
|
||||
while (!server_ready)
|
||||
{
|
||||
io.restart();
|
||||
io.run_one();
|
||||
}
|
||||
|
||||
client_request request{};
|
||||
get_next(io, server, buffer, request);
|
||||
EXPECT(request.method() == boost::beast::http::verb::get);
|
||||
EXPECT(request.body().empty());
|
||||
EXPECT(request.target() == "lws/foo");
|
||||
|
||||
send(server, "");
|
||||
|
||||
EXPECT(!client_ready);
|
||||
while (!client_ready)
|
||||
{
|
||||
io.restart();
|
||||
io.run_one();
|
||||
}
|
||||
|
||||
EXPECT(client.is_connected());
|
||||
} // end temp setup variables
|
||||
|
||||
SECTION("post")
|
||||
{
|
||||
const std::string request_body = "THE REQUEST";
|
||||
const std::string response = "THE RESPONSE";
|
||||
|
||||
epee::byte_slice actual;
|
||||
client.post_async("/posted", to_slice(request_body), [&] (auto, epee::byte_slice src) { actual = std::move(src); });
|
||||
|
||||
client_request request{};
|
||||
get_next(io, server, buffer, request);
|
||||
EXPECT(request.method() == boost::beast::http::verb::post);
|
||||
EXPECT(request.body() == request_body);
|
||||
EXPECT(request.target() == "lws/posted");
|
||||
|
||||
send(server, response);
|
||||
|
||||
while (actual.empty())
|
||||
{
|
||||
io.restart();
|
||||
io.run_one();
|
||||
}
|
||||
|
||||
EXPECT(to_string(actual) == response);
|
||||
}
|
||||
|
||||
SECTION("post/get x3")
|
||||
{
|
||||
using verb = boost::beast::http::verb;
|
||||
const std::array<verb, 3> verbs{{verb::post, verb::get, verb::post}};
|
||||
const std::array<std::string, 3> targets{{"/post1", "/get1", "/post2"}};
|
||||
const std::array<std::string, 3> requests{{"REQ1", "", "REQ3"}};
|
||||
const std::array<std::string, 3> responses{{"RESP1", "RESP2", "RESP3"}};
|
||||
std::array<epee::byte_slice, 3> actuals;
|
||||
|
||||
client.post_async(targets[0], to_slice(requests[0]), [&] (auto, epee::byte_slice src) { actuals[0] = std::move(src); });
|
||||
client.get_async(targets[1], [&] (auto, epee::byte_slice src) { actuals[1] = std::move(src); });
|
||||
client.post_async(targets[2], to_slice(requests[2]), [&] (auto, epee::byte_slice src) { actuals[2] = std::move(src); });
|
||||
|
||||
for (std::size_t i = 0; i < requests.size(); ++i)
|
||||
{
|
||||
client_request request{};
|
||||
get_next(io, server, buffer, request);
|
||||
EXPECT(request.method() == verbs[i]);
|
||||
EXPECT(request.body() == requests[i]);
|
||||
EXPECT(request.target() == "lws" + targets[i]);
|
||||
|
||||
send(server, responses[i]);
|
||||
}
|
||||
|
||||
while (actuals[2].empty())
|
||||
{
|
||||
io.restart();
|
||||
io.run_one();
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < actuals.size(); ++i)
|
||||
EXPECT(to_string(actuals[i]) == responses[i]);
|
||||
}
|
||||
|
||||
SECTION("invalid host")
|
||||
{
|
||||
std::error_code actual;
|
||||
client.init(io, ".test", "", 80, epee::net_utils::ssl_support_t::e_ssl_support_disabled);
|
||||
client.get_async("/", [&] (std::error_code error, auto) { actual = error; });
|
||||
|
||||
while (actual == std::error_code{})
|
||||
{
|
||||
io.restart();
|
||||
io.run_one();
|
||||
}
|
||||
|
||||
EXPECT(actual == lwsf::internal::http::error::timeout);
|
||||
}
|
||||
|
||||
SECTION("connect timeout")
|
||||
{
|
||||
std::error_code actual;
|
||||
tcp::acceptor acceptor{io, tcp::endpoint(tcp::v4(), 0)};
|
||||
acceptor.listen();
|
||||
|
||||
client.init(io, "127.0.0.1", "", acceptor.local_endpoint().port(), epee::net_utils::ssl_support_t::e_ssl_support_disabled);
|
||||
client.get_async("/", [&] (std::error_code error, auto) { actual = error; });
|
||||
|
||||
io.restart();
|
||||
io.run();
|
||||
|
||||
EXPECT(actual == lwsf::internal::http::error::timeout);
|
||||
}
|
||||
|
||||
SECTION("response timeout")
|
||||
{
|
||||
std::error_code actual;
|
||||
client.get_async("/get", [&] (std::error_code error, auto) { actual = error; });
|
||||
|
||||
io.restart();
|
||||
io.run();
|
||||
|
||||
EXPECT(actual == lwsf::internal::http::error::timeout);
|
||||
}
|
||||
} // SETUP
|
||||
}
|
||||
Reference in New Issue
Block a user