// Copyright (c) 2018-2020, 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 #include #include #include #include #include #include "byte_slice.h" // monero/contrib/epee/include #include "common/expect.h" // monero/src #include "rpc/message.h" // monero/src #include "rpc/daemon_pub.h" #include "rpc/rates.h" #include "util/source_location.h" namespace lws { namespace rpc { namespace detail { struct close { void operator()(void* ptr) const noexcept { if (ptr) zmq_close(ptr); } }; using socket = std::unique_ptr; struct context; } //! Abstraction for ZMQ RPC client. Only `get_rates()` thread-safe; use `clone()`. class client { std::shared_ptr ctx; detail::socket daemon; detail::socket daemon_sub; detail::socket signal_sub; explicit client(std::shared_ptr ctx) noexcept : ctx(std::move(ctx)), daemon(), daemon_sub(), signal_sub() {} //! Expect `response` as the next message payload unless error. expect get_response(cryptonote::rpc::Message& response, std::chrono::seconds timeout, source_location loc); public: //! A client with no connection (all send/receive functions fail). explicit client() noexcept : ctx(), daemon(), daemon_sub(), signal_sub() {} static expect make(std::shared_ptr ctx) noexcept; client(client&&) = default; client(client const&) = delete; ~client() noexcept; client& operator=(client&&) = default; client& operator=(client const&) = delete; /*! \note `watch_scan_signals()` status is not cloned. \note The copy is not cheap - it creates a new ZMQ socket. \return A client connected to same daemon as `this`. */ expect clone() const noexcept { return make(ctx); } //! \return True if `this` is valid (i.e. not default or moved from). explicit operator bool() const noexcept { return ctx != nullptr; } //! `wait`, `send`, and `receive` will watch for `raise_abort_scan()`. expect watch_scan_signals() noexcept; //! Wait for new block announce or internal timeout. expect wait_for_block(); //! \return A JSON message for RPC request `M`. template static epee::byte_slice make_message(char const* const name, const M& message) { return cryptonote::rpc::FullMessage::getRequest(name, message, 0); } /*! Queue `message` for sending to daemon. If the queue is full, wait a maximum of `timeout` seconds or until `context::raise_abort_scan` or `context::raise_abort_process()` is called. */ expect send(epee::byte_slice message, std::chrono::seconds timeout) noexcept; //! \return Next available RPC message response from server expect get_message(std::chrono::seconds timeout); //! \return RPC response `M`, waiting a max of `timeout` seconds. Log errors as from `loc`. template expect receive(const std::chrono::seconds timeout, const source_location loc = {}) { M response{}; MONERO_CHECK(get_response(response, timeout, loc)); return response; } /*! \note This is the one function that IS thread-safe. Multiple threads can call this function with the same `this` argument. \return Recent exchange rates. */ expect get_rates() const; }; //! Owns ZMQ context, and ZMQ PUB socket for signalling child `client`s. class context { std::shared_ptr ctx; explicit context(std::shared_ptr ctx) : ctx(std::move(ctx)) {} public: /*! Use `daemon_addr` for call child client objects. \throw std::bad_alloc if internal `shared_ptr` allocation failed. \throw std::system_error if any ZMQ errors occur. \note All errors are exceptions; no recovery can occur. \param daemon_addr Location of ZMQ enabled `monerod` RPC. \param rates_interval Frequency to retrieve exchange rates. Set value to `<= 0` to disable exchange rate retrieval. */ static context make(std::string daemon_addr, std::string sub_addr, std::chrono::minutes rates_interval); context(context&&) = default; context(context const&) = delete; //! Calls `raise_abort_process()`. Clients can safely destruct later. ~context() noexcept; context& operator=(context&&) = default; context& operator=(context const&) = delete; // Do not create clone method, only one of these should exist right now. //! \return The full address of the monerod ZMQ daemon. std::string const& daemon_address() const; //! \return Client connection. Thread-safe. expect connect() const noexcept { return client::make(ctx); } /*! All block `client::send`, `client::receive`, and `client::wait` calls originating from `this` object AND whose `watch_scan_signal` method was invoked, will immediately return with `lws::error::kSignlAbortScan`. This is NOT signal-safe NOR signal-safe NOR thread-safe. */ expect raise_abort_scan() noexcept; /*! All blocked `client::send`, `client::receive`, and `client::wait` calls originating from `this` object will immediately return with `lws::error::kSignalAbortProcess`. This call is NOT signal-safe NOR thread-safe. */ expect raise_abort_process() noexcept; /*! Retrieve exchange rates, if enabled and past cache interval. Not thread-safe (this can be invoked from one thread only, but this is thread-safe with `client::get_rates()`). All clients will see new rates immediately. \return Rates iff they were updated. */ expect> retrieve_rates(); }; } // rpc } // lws