mirror of https://github.com/oxen-io/oxen-core.git
Convert quorumnet to loki-mq
This adds the loki-mq dependency and replaces SNNetwork with it (along with some syntax updates for how loki-mq changed a bit from SNNetwork). This also replaces common/hex.h and common/string_view.h with loki-mq's faster (hex) and more complete and tested (string_view) implementations.
This commit is contained in:
parent
5b97ff6e9c
commit
c98688fd84
|
@ -15,3 +15,6 @@
|
|||
[submodule "external/randomx"]
|
||||
path = external/randomx
|
||||
url = https://github.com/loki-project/RandomX
|
||||
[submodule "external/loki-mq"]
|
||||
path = external/loki-mq
|
||||
url = https://github.com/loki-project/loki-mq.git
|
||||
|
|
|
@ -183,8 +183,18 @@ if(NOT MANUAL_SUBMODULES)
|
|||
if (upToDate)
|
||||
message(STATUS "Submodule '${relative_path}' is up-to-date")
|
||||
else()
|
||||
message(FATAL_ERROR "Submodule '${relative_path}' is not up-to-date. Please update with\ngit submodule update --init --force ${relative_path}\nor run cmake with -DMANUAL_SUBMODULES=1")
|
||||
message(FATAL_ERROR "Submodule '${relative_path}' is not up-to-date. Please update with\ngit submodule update --init --recursive\nor run cmake with -DMANUAL_SUBMODULES=1")
|
||||
endif()
|
||||
|
||||
# Extra arguments check nested submodules
|
||||
foreach(submod ${ARGN})
|
||||
execute_process(COMMAND git rev-parse "HEAD" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path}/${submod} OUTPUT_VARIABLE localHead)
|
||||
execute_process(COMMAND git rev-parse "HEAD:${submod}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${relative_path} OUTPUT_VARIABLE checkedHead)
|
||||
string(COMPARE EQUAL "${localHead}" "${checkedHead}" upToDate)
|
||||
if (NOT upToDate)
|
||||
message(FATAL_ERROR "Nested submodule '${relative_path}/${submod}' is not up-to-date. Please update with\ngit submodule update --init --recursive\nor run cmake with -DMANUAL_SUBMODULES=1")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction ()
|
||||
|
||||
message(STATUS "Checking submodules")
|
||||
|
@ -193,6 +203,7 @@ if(NOT MANUAL_SUBMODULES)
|
|||
check_submodule(external/rapidjson)
|
||||
check_submodule(external/trezor-common)
|
||||
check_submodule(external/randomx)
|
||||
check_submodule(external/loki-mq mapbox-variant cppzmq)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -275,7 +286,7 @@ endif()
|
|||
# elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*")
|
||||
# set(BSDI TRUE)
|
||||
|
||||
include_directories(external/rapidjson/include src contrib/epee/include external/cppzmq external)
|
||||
include_directories(external/rapidjson/include src contrib/epee/include external)
|
||||
|
||||
if(APPLE)
|
||||
include_directories(SYSTEM /usr/include/malloc)
|
||||
|
@ -853,10 +864,6 @@ if (LOKI_DEBUG_SHORT_PROOFS)
|
|||
add_definitions(-DUPTIME_PROOF_BASE_MINUTE=3) # 20x faster uptime proofs
|
||||
endif()
|
||||
|
||||
if(NOT ZMQ_LIBRARIES) # may be already set (such as by contrib/depends toolchain)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(ZMQ REQUIRED libzmq)
|
||||
endif()
|
||||
|
||||
add_library(sodium INTERFACE)
|
||||
if(NOT SODIUM_LIBRARIES)
|
||||
|
|
|
@ -89,3 +89,4 @@ endif()
|
|||
add_subdirectory(db_drivers)
|
||||
add_subdirectory(easylogging++)
|
||||
add_subdirectory(randomx EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(loki-mq)
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
File diff suppressed because it is too large
Load Diff
|
@ -1,457 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2016-2017 ZeroMQ community
|
||||
Copyright (c) 2016 VOCA AS / Harald Nøkland
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __ZMQ_ADDON_HPP_INCLUDED__
|
||||
#define __ZMQ_ADDON_HPP_INCLUDED__
|
||||
|
||||
#include <zmq.hpp>
|
||||
|
||||
#include <deque>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#ifdef ZMQ_CPP11
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#endif
|
||||
|
||||
namespace zmq
|
||||
{
|
||||
#ifdef ZMQ_HAS_RVALUE_REFS
|
||||
|
||||
/*
|
||||
This class handles multipart messaging. It is the C++ equivalent of zmsg.h,
|
||||
which is part of CZMQ (the high-level C binding). Furthermore, it is a major
|
||||
improvement compared to zmsg.hpp, which is part of the examples in the ØMQ
|
||||
Guide. Unnecessary copying is avoided by using move semantics to efficiently
|
||||
add/remove parts.
|
||||
*/
|
||||
class multipart_t
|
||||
{
|
||||
private:
|
||||
std::deque<message_t> m_parts;
|
||||
|
||||
public:
|
||||
typedef std::deque<message_t>::iterator iterator;
|
||||
typedef std::deque<message_t>::const_iterator const_iterator;
|
||||
|
||||
typedef std::deque<message_t>::reverse_iterator reverse_iterator;
|
||||
typedef std::deque<message_t>::const_reverse_iterator const_reverse_iterator;
|
||||
|
||||
// Default constructor
|
||||
multipart_t() {}
|
||||
|
||||
// Construct from socket receive
|
||||
multipart_t(socket_t &socket) { recv(socket); }
|
||||
|
||||
// Construct from memory block
|
||||
multipart_t(const void *src, size_t size) { addmem(src, size); }
|
||||
|
||||
// Construct from string
|
||||
multipart_t(const std::string &string) { addstr(string); }
|
||||
|
||||
// Construct from message part
|
||||
multipart_t(message_t &&message) { add(std::move(message)); }
|
||||
|
||||
// Move constructor
|
||||
multipart_t(multipart_t &&other) { m_parts = std::move(other.m_parts); }
|
||||
|
||||
// Move assignment operator
|
||||
multipart_t &operator=(multipart_t &&other)
|
||||
{
|
||||
m_parts = std::move(other.m_parts);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destructor
|
||||
virtual ~multipart_t() { clear(); }
|
||||
|
||||
message_t &operator[](size_t n) { return m_parts[n]; }
|
||||
|
||||
const message_t &operator[](size_t n) const { return m_parts[n]; }
|
||||
|
||||
message_t &at(size_t n) { return m_parts.at(n); }
|
||||
|
||||
const message_t &at(size_t n) const { return m_parts.at(n); }
|
||||
|
||||
iterator begin() { return m_parts.begin(); }
|
||||
|
||||
const_iterator begin() const { return m_parts.begin(); }
|
||||
|
||||
const_iterator cbegin() const { return m_parts.cbegin(); }
|
||||
|
||||
reverse_iterator rbegin() { return m_parts.rbegin(); }
|
||||
|
||||
const_reverse_iterator rbegin() const { return m_parts.rbegin(); }
|
||||
|
||||
iterator end() { return m_parts.end(); }
|
||||
|
||||
const_iterator end() const { return m_parts.end(); }
|
||||
|
||||
const_iterator cend() const { return m_parts.cend(); }
|
||||
|
||||
reverse_iterator rend() { return m_parts.rend(); }
|
||||
|
||||
const_reverse_iterator rend() const { return m_parts.rend(); }
|
||||
|
||||
// Delete all parts
|
||||
void clear() { m_parts.clear(); }
|
||||
|
||||
// Get number of parts
|
||||
size_t size() const { return m_parts.size(); }
|
||||
|
||||
// Check if number of parts is zero
|
||||
bool empty() const { return m_parts.empty(); }
|
||||
|
||||
// Receive multipart message from socket
|
||||
bool recv(socket_t &socket, int flags = 0)
|
||||
{
|
||||
clear();
|
||||
bool more = true;
|
||||
while (more) {
|
||||
message_t message;
|
||||
#ifdef ZMQ_CPP11
|
||||
if (!socket.recv(message, static_cast<recv_flags>(flags)))
|
||||
return false;
|
||||
#else
|
||||
if (!socket.recv(&message, flags))
|
||||
return false;
|
||||
#endif
|
||||
more = message.more();
|
||||
add(std::move(message));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Send multipart message to socket
|
||||
bool send(socket_t &socket, int flags = 0)
|
||||
{
|
||||
flags &= ~(ZMQ_SNDMORE);
|
||||
bool more = size() > 0;
|
||||
while (more) {
|
||||
message_t message = pop();
|
||||
more = size() > 0;
|
||||
#ifdef ZMQ_CPP11
|
||||
if (!socket.send(message,
|
||||
static_cast<send_flags>((more ? ZMQ_SNDMORE : 0) | flags)))
|
||||
return false;
|
||||
#else
|
||||
if (!socket.send(message, (more ? ZMQ_SNDMORE : 0) | flags))
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Concatenate other multipart to front
|
||||
void prepend(multipart_t &&other)
|
||||
{
|
||||
while (!other.empty())
|
||||
push(other.remove());
|
||||
}
|
||||
|
||||
// Concatenate other multipart to back
|
||||
void append(multipart_t &&other)
|
||||
{
|
||||
while (!other.empty())
|
||||
add(other.pop());
|
||||
}
|
||||
|
||||
// Push memory block to front
|
||||
void pushmem(const void *src, size_t size)
|
||||
{
|
||||
m_parts.push_front(message_t(src, size));
|
||||
}
|
||||
|
||||
// Push memory block to back
|
||||
void addmem(const void *src, size_t size)
|
||||
{
|
||||
m_parts.push_back(message_t(src, size));
|
||||
}
|
||||
|
||||
// Push string to front
|
||||
void pushstr(const std::string &string)
|
||||
{
|
||||
m_parts.push_front(message_t(string.data(), string.size()));
|
||||
}
|
||||
|
||||
// Push string to back
|
||||
void addstr(const std::string &string)
|
||||
{
|
||||
m_parts.push_back(message_t(string.data(), string.size()));
|
||||
}
|
||||
|
||||
// Push type (fixed-size) to front
|
||||
template<typename T> void pushtyp(const T &type)
|
||||
{
|
||||
static_assert(!std::is_same<T, std::string>::value,
|
||||
"Use pushstr() instead of pushtyp<std::string>()");
|
||||
m_parts.push_front(message_t(&type, sizeof(type)));
|
||||
}
|
||||
|
||||
// Push type (fixed-size) to back
|
||||
template<typename T> void addtyp(const T &type)
|
||||
{
|
||||
static_assert(!std::is_same<T, std::string>::value,
|
||||
"Use addstr() instead of addtyp<std::string>()");
|
||||
m_parts.push_back(message_t(&type, sizeof(type)));
|
||||
}
|
||||
|
||||
// Push message part to front
|
||||
void push(message_t &&message) { m_parts.push_front(std::move(message)); }
|
||||
|
||||
// Push message part to back
|
||||
void add(message_t &&message) { m_parts.push_back(std::move(message)); }
|
||||
|
||||
// Pop string from front
|
||||
std::string popstr()
|
||||
{
|
||||
std::string string(m_parts.front().data<char>(), m_parts.front().size());
|
||||
m_parts.pop_front();
|
||||
return string;
|
||||
}
|
||||
|
||||
// Pop type (fixed-size) from front
|
||||
template<typename T> T poptyp()
|
||||
{
|
||||
static_assert(!std::is_same<T, std::string>::value,
|
||||
"Use popstr() instead of poptyp<std::string>()");
|
||||
if (sizeof(T) != m_parts.front().size())
|
||||
throw std::runtime_error(
|
||||
"Invalid type, size does not match the message size");
|
||||
T type = *m_parts.front().data<T>();
|
||||
m_parts.pop_front();
|
||||
return type;
|
||||
}
|
||||
|
||||
// Pop message part from front
|
||||
message_t pop()
|
||||
{
|
||||
message_t message = std::move(m_parts.front());
|
||||
m_parts.pop_front();
|
||||
return message;
|
||||
}
|
||||
|
||||
// Pop message part from back
|
||||
message_t remove()
|
||||
{
|
||||
message_t message = std::move(m_parts.back());
|
||||
m_parts.pop_back();
|
||||
return message;
|
||||
}
|
||||
|
||||
// get message part from front
|
||||
const message_t &front()
|
||||
{
|
||||
return m_parts.front();
|
||||
}
|
||||
|
||||
// get message part from back
|
||||
const message_t &back()
|
||||
{
|
||||
return m_parts.back();
|
||||
}
|
||||
|
||||
// Get pointer to a specific message part
|
||||
const message_t *peek(size_t index) const { return &m_parts[index]; }
|
||||
|
||||
// Get a string copy of a specific message part
|
||||
std::string peekstr(size_t index) const
|
||||
{
|
||||
std::string string(m_parts[index].data<char>(), m_parts[index].size());
|
||||
return string;
|
||||
}
|
||||
|
||||
// Peek type (fixed-size) from front
|
||||
template<typename T> T peektyp(size_t index) const
|
||||
{
|
||||
static_assert(!std::is_same<T, std::string>::value,
|
||||
"Use peekstr() instead of peektyp<std::string>()");
|
||||
if (sizeof(T) != m_parts[index].size())
|
||||
throw std::runtime_error(
|
||||
"Invalid type, size does not match the message size");
|
||||
T type = *m_parts[index].data<T>();
|
||||
return type;
|
||||
}
|
||||
|
||||
// Create multipart from type (fixed-size)
|
||||
template<typename T> static multipart_t create(const T &type)
|
||||
{
|
||||
multipart_t multipart;
|
||||
multipart.addtyp(type);
|
||||
return multipart;
|
||||
}
|
||||
|
||||
// Copy multipart
|
||||
multipart_t clone() const
|
||||
{
|
||||
multipart_t multipart;
|
||||
for (size_t i = 0; i < size(); i++)
|
||||
multipart.addmem(m_parts[i].data(), m_parts[i].size());
|
||||
return multipart;
|
||||
}
|
||||
|
||||
// Dump content to string
|
||||
std::string str() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < m_parts.size(); i++) {
|
||||
const unsigned char *data = m_parts[i].data<unsigned char>();
|
||||
size_t size = m_parts[i].size();
|
||||
|
||||
// Dump the message as text or binary
|
||||
bool isText = true;
|
||||
for (size_t j = 0; j < size; j++) {
|
||||
if (data[j] < 32 || data[j] > 127) {
|
||||
isText = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ss << "\n[" << std::dec << std::setw(3) << std::setfill('0') << size
|
||||
<< "] ";
|
||||
if (size >= 1000) {
|
||||
ss << "... (to big to print)";
|
||||
continue;
|
||||
}
|
||||
for (size_t j = 0; j < size; j++) {
|
||||
if (isText)
|
||||
ss << static_cast<char>(data[j]);
|
||||
else
|
||||
ss << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<short>(data[j]);
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Check if equal to other multipart
|
||||
bool equal(const multipart_t *other) const
|
||||
{
|
||||
if (size() != other->size())
|
||||
return false;
|
||||
for (size_t i = 0; i < size(); i++)
|
||||
if (*peek(i) != *other->peek(i))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Disable implicit copying (moving is more efficient)
|
||||
multipart_t(const multipart_t &other) ZMQ_DELETED_FUNCTION;
|
||||
void operator=(const multipart_t &other) ZMQ_DELETED_FUNCTION;
|
||||
}; // class multipart_t
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &os, const multipart_t &msg)
|
||||
{
|
||||
return os << msg.str();
|
||||
}
|
||||
|
||||
#endif // ZMQ_HAS_RVALUE_REFS
|
||||
|
||||
#if defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER)
|
||||
class active_poller_t
|
||||
{
|
||||
public:
|
||||
active_poller_t() = default;
|
||||
~active_poller_t() = default;
|
||||
|
||||
active_poller_t(const active_poller_t &) = delete;
|
||||
active_poller_t &operator=(const active_poller_t &) = delete;
|
||||
|
||||
active_poller_t(active_poller_t &&src) = default;
|
||||
active_poller_t &operator=(active_poller_t &&src) = default;
|
||||
|
||||
using handler_type = std::function<void(event_flags)>;
|
||||
|
||||
void add(zmq::socket_ref socket, event_flags events, handler_type handler)
|
||||
{
|
||||
auto it = decltype(handlers)::iterator{};
|
||||
auto inserted = bool{};
|
||||
std::tie(it, inserted) =
|
||||
handlers.emplace(socket,
|
||||
std::make_shared<handler_type>(std::move(handler)));
|
||||
try {
|
||||
base_poller.add(socket, events,
|
||||
inserted && *(it->second) ? it->second.get() : nullptr);
|
||||
need_rebuild |= inserted;
|
||||
}
|
||||
catch (const zmq::error_t &) {
|
||||
// rollback
|
||||
if (inserted) {
|
||||
handlers.erase(socket);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void remove(zmq::socket_ref socket)
|
||||
{
|
||||
base_poller.remove(socket);
|
||||
handlers.erase(socket);
|
||||
need_rebuild = true;
|
||||
}
|
||||
|
||||
void modify(zmq::socket_ref socket, event_flags events)
|
||||
{
|
||||
base_poller.modify(socket, events);
|
||||
}
|
||||
|
||||
size_t wait(std::chrono::milliseconds timeout)
|
||||
{
|
||||
if (need_rebuild) {
|
||||
poller_events.resize(handlers.size());
|
||||
poller_handlers.clear();
|
||||
poller_handlers.reserve(handlers.size());
|
||||
for (const auto &handler : handlers) {
|
||||
poller_handlers.push_back(handler.second);
|
||||
}
|
||||
need_rebuild = false;
|
||||
}
|
||||
const auto count = base_poller.wait_all(poller_events, timeout);
|
||||
std::for_each(poller_events.begin(), poller_events.begin() + static_cast<ptrdiff_t>(count),
|
||||
[](decltype(base_poller)::event_type &event) {
|
||||
if (event.user_data != nullptr)
|
||||
(*event.user_data)(event.events);
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
ZMQ_NODISCARD bool empty() const noexcept { return handlers.empty(); }
|
||||
|
||||
size_t size() const noexcept { return handlers.size(); }
|
||||
|
||||
private:
|
||||
bool need_rebuild{false};
|
||||
|
||||
poller_t<handler_type> base_poller{};
|
||||
std::unordered_map<socket_ref, std::shared_ptr<handler_type>> handlers{};
|
||||
std::vector<decltype(base_poller)::event_type> poller_events{};
|
||||
std::vector<std::shared_ptr<handler_type>> poller_handlers{};
|
||||
}; // class active_poller_t
|
||||
#endif // defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER)
|
||||
|
||||
|
||||
} // namespace zmq
|
||||
|
||||
#endif // __ZMQ_ADDON_HPP_INCLUDED__
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 512086613a79a014d5b18da28dff3828bf76a1aa
|
|
@ -70,7 +70,6 @@ add_subdirectory(net)
|
|||
add_subdirectory(mnemonics)
|
||||
add_subdirectory(wallet)
|
||||
add_subdirectory(cryptonote_protocol)
|
||||
add_subdirectory(quorumnet)
|
||||
|
||||
if(NOT IOS)
|
||||
if (NOT BUILD_INTEGRATION)
|
||||
|
|
|
@ -68,6 +68,7 @@ add_dependencies(common generate_translations_header)
|
|||
target_link_libraries(common
|
||||
PUBLIC
|
||||
cncrypto
|
||||
lokimq::lokimq
|
||||
PRIVATE
|
||||
libunbound
|
||||
Boost::regex
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
namespace hex
|
||||
{
|
||||
constexpr bool char_is_hex(char c)
|
||||
{
|
||||
bool result = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr char from_hex_digit(char x) {
|
||||
return
|
||||
(x >= '0' && x <= '9') ? x - '0' :
|
||||
(x >= 'a' && x <= 'f') ? x - ('a' - 10):
|
||||
(x >= 'A' && x <= 'F') ? x - ('A' - 10):
|
||||
0;
|
||||
}
|
||||
constexpr char from_hex_pair(char a, char b) { return (from_hex_digit(a) << 4) | from_hex_digit(b); }
|
||||
|
||||
// Creates a string from a character sequence of hex digits. Undefined behaviour if any characters
|
||||
// are not in [0-9a-fA-F] or if the input sequence length is not even.
|
||||
template <typename It>
|
||||
std::string from_hex(It begin, It end) {
|
||||
using std::distance;
|
||||
using std::next;
|
||||
assert(distance(begin, end) % 2 == 0);
|
||||
std::string raw;
|
||||
raw.reserve(distance(begin, end) / 2);
|
||||
while (begin != end) {
|
||||
char a = *begin++;
|
||||
char b = *begin++;
|
||||
raw += from_hex_pair(a, b);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#ifdef __cpp_lib_string_view
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
namespace common
|
||||
{
|
||||
#ifdef __cpp_lib_string_view
|
||||
using string_view = std::string_view;
|
||||
#else
|
||||
class simple_string_view {
|
||||
const char *_data;
|
||||
size_t _size;
|
||||
using char_traits = std::char_traits<char>;
|
||||
public:
|
||||
constexpr simple_string_view() noexcept : _data{nullptr}, _size{0} {}
|
||||
constexpr simple_string_view(const simple_string_view &) noexcept = default;
|
||||
simple_string_view(const std::string &str) : _data{str.data()}, _size{str.size()} {}
|
||||
constexpr simple_string_view(const char *data, size_t size) noexcept : _data{data}, _size{size} {}
|
||||
simple_string_view(const char *data) : _data{data}, _size{std::char_traits<char>::length(data)} {}
|
||||
simple_string_view &operator=(const simple_string_view &) = default;
|
||||
constexpr const char *data() const { return _data; }
|
||||
constexpr size_t size() const { return _size; }
|
||||
constexpr bool empty() { return _size == 0; }
|
||||
operator std::string() const { return {_data, _size}; }
|
||||
const char *begin() const { return _data; }
|
||||
const char *end() const { return _data + _size; }
|
||||
};
|
||||
bool operator==(simple_string_view lhs, simple_string_view rhs) {
|
||||
return lhs.size() == rhs.size() && 0 == std::char_traits<char>::compare(lhs.data(), rhs.data(), lhs.size());
|
||||
};
|
||||
bool operator!=(simple_string_view lhs, simple_string_view rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
std::ostream &operator<<(std::ostream &os, const simple_string_view &s) { return os.write(s.data(), s.size()); }
|
||||
using string_view = simple_string_view;
|
||||
#endif
|
||||
}; // namespace common
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "checkpoints/checkpoints.h"
|
||||
#include "common/loki.h"
|
||||
#include "common/hex.h"
|
||||
#include "common/util.h"
|
||||
#include "common/base32z.h"
|
||||
#include "crypto/hash.h"
|
||||
|
@ -12,6 +11,8 @@
|
|||
#include "cryptonote_basic/tx_extra.h"
|
||||
#include "cryptonote_core/blockchain.h"
|
||||
|
||||
#include <lokimq/hex.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
extern "C"
|
||||
|
@ -541,25 +542,20 @@ bool validate_lns_value(cryptonote::network_type nettype, mapping_type type, std
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t val_index = 0; val_index < value.size(); val_index += 2)
|
||||
if (!lokimq::is_hex(value))
|
||||
{
|
||||
char a = value[val_index];
|
||||
char b = value[val_index + 1];
|
||||
if (hex::char_is_hex(a) && hex::char_is_hex(b))
|
||||
if (reason)
|
||||
{
|
||||
if (blob) // NOTE: Given blob, write the binary output
|
||||
blob->buffer.data()[blob->len++] = hex::from_hex_pair(a, b);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reason)
|
||||
{
|
||||
err_stream << "LNS type=" << type <<", specifies name -> value mapping where the value is not a hex string given value=" << value;
|
||||
*reason = err_stream.str();
|
||||
}
|
||||
return false;
|
||||
err_stream << "LNS type=" << type <<", specifies name -> value mapping where the value is not a hex string given value=" << value;
|
||||
*reason = err_stream.str();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (blob) // NOTE: Given blob, write the binary output
|
||||
{
|
||||
blob->len = value.size() / 2;
|
||||
assert(blob->len <= blob->buffer.size());
|
||||
lokimq::from_hex(value.begin(), value.end(), blob->buffer.begin());
|
||||
}
|
||||
|
||||
if (type == mapping_type::session)
|
||||
|
|
|
@ -38,7 +38,6 @@ target_link_libraries(cryptonote_protocol
|
|||
PUBLIC
|
||||
p2p
|
||||
PRIVATE
|
||||
quorumnet
|
||||
miniupnpc
|
||||
easylogging
|
||||
extra)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2019, The Loki Project
|
||||
// Copyright (c) 2019-2020, The Loki Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
|
@ -32,12 +32,13 @@
|
|||
#include "cryptonote_core/service_node_rules.h"
|
||||
#include "cryptonote_core/tx_blink.h"
|
||||
#include "cryptonote_core/tx_pool.h"
|
||||
#include "quorumnet/sn_network.h"
|
||||
#include "quorumnet/conn_matrix.h"
|
||||
#include "quorumnet_conn_matrix.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "common/random.h"
|
||||
#include "common/lock.h"
|
||||
|
||||
#include <lokimq/lokimq.h>
|
||||
#include <lokimq/hex.h>
|
||||
#include <shared_mutex>
|
||||
|
||||
#undef LOKI_DEFAULT_LOG_CATEGORY
|
||||
|
@ -48,8 +49,9 @@ namespace quorumnet {
|
|||
namespace {
|
||||
|
||||
using namespace service_nodes;
|
||||
using namespace std::string_literals;
|
||||
using namespace std::chrono_literals;
|
||||
using namespace std::literals;
|
||||
using namespace lokimq;
|
||||
using namespace lokimq::literals;
|
||||
|
||||
using blink_tx = cryptonote::blink_tx;
|
||||
|
||||
|
@ -67,7 +69,7 @@ struct pending_signature_hash {
|
|||
using pending_signature_set = std::unordered_set<pending_signature, pending_signature_hash>;
|
||||
|
||||
struct SNNWrapper {
|
||||
SNNetwork snn;
|
||||
LokiMQ lmq;
|
||||
cryptonote::core &core;
|
||||
|
||||
// Track submitted blink txes here; unlike the blinks stored in the mempool we store these ones
|
||||
|
@ -89,7 +91,9 @@ struct SNNWrapper {
|
|||
|
||||
template <typename... Args>
|
||||
SNNWrapper(cryptonote::core &core, Args &&...args) :
|
||||
snn{std::forward<Args>(args)...}, core{core} {}
|
||||
lmq{std::forward<Args>(args)...}, core{core} {
|
||||
lmq.log_level(LogLevel::trace);
|
||||
}
|
||||
|
||||
static SNNWrapper &from(void* obj) {
|
||||
assert(obj);
|
||||
|
@ -103,7 +107,7 @@ std::string get_data_as_string(const T &key) {
|
|||
return {reinterpret_cast<const char *>(&key), sizeof(key)};
|
||||
}
|
||||
|
||||
crypto::x25519_public_key x25519_from_string(const std::string &pubkey) {
|
||||
crypto::x25519_public_key x25519_from_string(string_view pubkey) {
|
||||
crypto::x25519_public_key x25519_pub = crypto::x25519_public_key::null();
|
||||
if (pubkey.size() == sizeof(crypto::x25519_public_key))
|
||||
std::memcpy(x25519_pub.data, pubkey.data(), pubkey.size());
|
||||
|
@ -150,24 +154,24 @@ constexpr el::Level easylogging_level(LogLevel level) {
|
|||
};
|
||||
return el::Level::Unknown;
|
||||
};
|
||||
bool snn_want_log(LogLevel level) {
|
||||
return ELPP->vRegistry()->allowed(easylogging_level(level), LOKI_DEFAULT_LOG_CATEGORY);
|
||||
}
|
||||
void snn_write_log(LogLevel level, const char *file, int line, std::string msg) {
|
||||
el::base::Writer(easylogging_level(level), file, line, ELPP_FUNC, el::base::DispatchAction::NormalLog).construct(LOKI_DEFAULT_LOG_CATEGORY) << msg;
|
||||
if (ELPP->vRegistry()->allowed(easylogging_level(level), LOKI_DEFAULT_LOG_CATEGORY))
|
||||
el::base::Writer(easylogging_level(level), file, line, ELPP_FUNC, el::base::DispatchAction::NormalLog).construct(LOKI_DEFAULT_LOG_CATEGORY) << msg;
|
||||
}
|
||||
|
||||
void setup_endpoints(SNNWrapper& snw);
|
||||
|
||||
void *new_snnwrapper(cryptonote::core &core, const std::string &bind) {
|
||||
auto keys = core.get_service_node_keys();
|
||||
auto peer_lookup = [&sn_list = core.get_service_node_list()](const std::string &x25519_pub) {
|
||||
auto peer_lookup = [&sn_list = core.get_service_node_list()](string_view x25519_pub) {
|
||||
return get_connect_string(sn_list, x25519_from_string(x25519_pub));
|
||||
};
|
||||
auto allow = [&sn_list = core.get_service_node_list()](const std::string &ip, const std::string &x25519_pubkey_str) {
|
||||
auto allow = [&sn_list = core.get_service_node_list()](string_view ip, string_view x25519_pubkey_str) -> Allow {
|
||||
auto x25519_pubkey = x25519_from_string(x25519_pubkey_str);
|
||||
auto pubkey = sn_list.get_pubkey_from_x25519(x25519_pubkey);
|
||||
MINFO("Accepting incoming " << (pubkey ? "SN" : "non-SN") << " connection authentication from ip/x25519/pubkey: " << ip << "/" << x25519_pubkey << "/" << pubkey);
|
||||
if (pubkey) {
|
||||
MINFO("Accepting incoming SN connection authentication from ip/x25519/pubkey: " << ip << "/" << x25519_pubkey << "/" << pubkey);
|
||||
return SNNetwork::allow::service_node;
|
||||
return {AuthLevel::none, true};
|
||||
}
|
||||
|
||||
// Public connection:
|
||||
|
@ -179,25 +183,29 @@ void *new_snnwrapper(cryptonote::core &core, const std::string &bind) {
|
|||
// (In theory we could extend this to also only allow SN
|
||||
// connections when in or near a blink/checkpoint/obligations/pulse quorum, but that
|
||||
// would get messy fast and probably have little practical benefit).
|
||||
return SNNetwork::allow::client;
|
||||
return {AuthLevel::none, false};
|
||||
};
|
||||
SNNWrapper *obj;
|
||||
if (!keys) {
|
||||
MINFO("Starting remote-only quorumnet instance");
|
||||
|
||||
obj = new SNNWrapper(core, peer_lookup, allow, snn_want_log, snn_write_log);
|
||||
} else {
|
||||
std::string pubkey, seckey;
|
||||
bool sn;
|
||||
if (keys) {
|
||||
MINFO("Starting quorumnet listener on " << bind << " with x25519 pubkey " << keys->pub_x25519);
|
||||
obj = new SNNWrapper(core,
|
||||
get_data_as_string(keys->pub_x25519),
|
||||
get_data_as_string(keys->key_x25519.data),
|
||||
std::vector<std::string>{{bind}},
|
||||
peer_lookup,
|
||||
allow,
|
||||
snn_want_log, snn_write_log);
|
||||
pubkey = get_data_as_string(keys->pub_x25519);
|
||||
seckey = get_data_as_string(keys->key_x25519.data);
|
||||
sn = true;
|
||||
} else {
|
||||
MINFO("Starting remote-only lokimq instance");
|
||||
sn = false;
|
||||
}
|
||||
|
||||
obj->snn.data = obj; // Provide pointer to the instance for callbacks
|
||||
obj = new SNNWrapper(core, pubkey, seckey, sn, std::move(peer_lookup), snn_write_log);
|
||||
|
||||
setup_endpoints(*obj);
|
||||
|
||||
if (sn)
|
||||
obj->lmq.listen_curve(bind, allow);
|
||||
|
||||
obj->lmq.start();
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
@ -245,7 +253,7 @@ public:
|
|||
|
||||
/// Singleton wrapper around peer_info
|
||||
peer_info(
|
||||
SNNWrapper &snw,
|
||||
SNNWrapper& snw,
|
||||
quorum_type q_type,
|
||||
std::shared_ptr<const quorum> &quorum,
|
||||
bool opportunistic = true,
|
||||
|
@ -264,13 +272,13 @@ public:
|
|||
/// pubkey is always added to this exclude list.
|
||||
template <typename QuorumIt>
|
||||
peer_info(
|
||||
SNNWrapper &snw,
|
||||
SNNWrapper& snw,
|
||||
quorum_type q_type,
|
||||
QuorumIt qbegin, QuorumIt qend,
|
||||
bool opportunistic = true,
|
||||
std::unordered_set<crypto::public_key> exclude = {}
|
||||
)
|
||||
: snn{snw.snn} {
|
||||
: lmq{snw.lmq} {
|
||||
|
||||
auto keys = snw.core.get_service_node_keys();
|
||||
assert(keys);
|
||||
|
@ -319,12 +327,12 @@ public:
|
|||
/// Relays a command and any number of serialized data to everyone we're supposed to relay to
|
||||
template <typename... T>
|
||||
void relay_to_peers(const std::string &cmd, const T &...data) {
|
||||
relay_to_peers_impl(cmd, std::array<send_option::serialized, sizeof...(T)>{send_option::serialized{data}...},
|
||||
relay_to_peers_impl(cmd, std::array<std::string, sizeof...(T)>{bt_serialize(data)...},
|
||||
std::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
private:
|
||||
SNNetwork &snn;
|
||||
LokiMQ &lmq;
|
||||
|
||||
/// Looks up a pubkey in known remotes and adds it to `peers`. If strong, it is added with an
|
||||
/// address, otherwise it is added with an empty address. If the element already exists, it
|
||||
|
@ -427,13 +435,13 @@ private:
|
|||
|
||||
/// Relays a command and pre-serialized data to everyone we're supposed to relay to
|
||||
template<size_t N, size_t... I>
|
||||
void relay_to_peers_impl(const std::string &cmd, std::array<send_option::serialized, N> relay_data, std::index_sequence<I...>) {
|
||||
void relay_to_peers_impl(const std::string &cmd, std::array<std::string, N> relay_data, std::index_sequence<I...>) {
|
||||
for (auto &peer : peers) {
|
||||
MTRACE("Relaying " << cmd << " to peer " << as_hex(peer.first) << (peer.second.empty() ? " (if connected)"s : " @ " + peer.second));
|
||||
MTRACE("Relaying " << cmd << " to peer " << to_hex(peer.first) << (peer.second.empty() ? " (if connected)"s : " @ " + peer.second));
|
||||
if (peer.second.empty())
|
||||
snn.send(peer.first, cmd, relay_data[I]..., send_option::optional{});
|
||||
lmq.send(peer.first, cmd, relay_data[I]..., send_option::optional{});
|
||||
else
|
||||
snn.send(peer.first, cmd, relay_data[I]..., send_option::hint{peer.second});
|
||||
lmq.send(peer.first, cmd, relay_data[I]..., send_option::hint{peer.second});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,8 +466,8 @@ bt_dict serialize_vote(const quorum_vote_t &vote) {
|
|||
return result;
|
||||
}
|
||||
|
||||
quorum_vote_t deserialize_vote(const bt_value &v) {
|
||||
const auto &d = boost::get<bt_dict>(v); // throws if not a bt_dict
|
||||
quorum_vote_t deserialize_vote(string_view v) {
|
||||
const auto &d = bt_deserialize<bt_dict>(v); // throws if not a bt_dict
|
||||
quorum_vote_t vote;
|
||||
vote.version = get_int<uint8_t>(d.at("v"));
|
||||
vote.type = get_enum<quorum_type>(d, "t");
|
||||
|
@ -467,11 +475,11 @@ quorum_vote_t deserialize_vote(const bt_value &v) {
|
|||
vote.group = get_enum<quorum_group>(d, "g");
|
||||
if (vote.group == quorum_group::invalid) throw std::invalid_argument("invalid vote group");
|
||||
vote.index_in_group = get_int<uint16_t>(d.at("i"));
|
||||
auto &sig = boost::get<std::string>(d.at("s"));
|
||||
auto &sig = d.at("s").get<std::string>();
|
||||
if (sig.size() != sizeof(vote.signature)) throw std::invalid_argument("invalid vote signature size");
|
||||
std::memcpy(&vote.signature, sig.data(), sizeof(vote.signature));
|
||||
if (vote.type == quorum_type::checkpointing) {
|
||||
auto &bh = boost::get<std::string>(d.at("bh"));
|
||||
auto &bh = d.at("bh").get<std::string>();
|
||||
if (bh.size() != sizeof(vote.checkpoint.block_hash.data)) throw std::invalid_argument("invalid vote checkpoint block hash");
|
||||
std::memcpy(vote.checkpoint.block_hash.data, bh.data(), sizeof(vote.checkpoint.block_hash.data));
|
||||
} else {
|
||||
|
@ -525,10 +533,8 @@ void relay_obligation_votes(void *obj, const std::vector<service_nodes::quorum_v
|
|||
snw.core.set_service_node_votes_relayed(relayed_votes);
|
||||
}
|
||||
|
||||
void handle_obligation_vote(SNNetwork::message &m, void *self) {
|
||||
auto &snw = SNNWrapper::from(self);
|
||||
|
||||
MDEBUG("Received a relayed obligation vote from " << as_hex(m.pubkey));
|
||||
void handle_obligation_vote(Message& m, SNNWrapper& snw) {
|
||||
MDEBUG("Received a relayed obligation vote from " << to_hex(m.conn.pubkey()));
|
||||
|
||||
if (m.data.size() != 1) {
|
||||
MINFO("Ignoring vote: expected 1 data part, not " << m.data.size());
|
||||
|
@ -558,10 +564,10 @@ void handle_obligation_vote(SNNetwork::message &m, void *self) {
|
|||
}
|
||||
|
||||
if (vvc.m_added_to_pool)
|
||||
relay_obligation_votes(self, std::move(vvote));
|
||||
relay_obligation_votes(&snw, std::move(vvote));
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
MWARNING("Deserialization of vote from " << as_hex(m.pubkey) << " failed: " << e.what());
|
||||
MWARNING("Deserialization of vote from " << to_hex(m.conn.pubkey()) << " failed: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -791,10 +797,10 @@ void process_blink_signatures(SNNWrapper &snw, const std::shared_ptr<blink_tx> &
|
|||
if (reply_tag && !reply_pubkey.empty()) {
|
||||
if (became_approved) {
|
||||
MINFO("Blink tx became approved; sending result back to originating node");
|
||||
snw.snn.send(reply_pubkey, "bl_good", bt_dict{{"!", reply_tag}}, send_option::optional{});
|
||||
snw.lmq.send(reply_pubkey, "bl_good", bt_serialize(bt_dict{{"!", reply_tag}}), send_option::optional{});
|
||||
} else if (became_rejected) {
|
||||
MINFO("Blink tx became rejected; sending result back to originating node");
|
||||
snw.snn.send(reply_pubkey, "bl_bad", bt_dict{{"!", reply_tag}}, send_option::optional{});
|
||||
snw.lmq.send(reply_pubkey, "bl_bad", bt_serialize(bt_dict{{"!", reply_tag}}), send_option::optional{});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -821,9 +827,7 @@ void process_blink_signatures(SNNWrapper &snw, const std::shared_ptr<blink_tx> &
|
|||
/// "#" - precomputed tx hash. This much match the actual hash of the transaction (the blink
|
||||
/// submission will fail immediately if it does not).
|
||||
///
|
||||
void handle_blink(SNNetwork::message &m, void *self) {
|
||||
auto &snw = SNNWrapper::from(self);
|
||||
|
||||
void handle_blink(lokimq::Message& m, SNNWrapper& snw) {
|
||||
// TODO: if someone sends an invalid tx (i.e. one that doesn't get to the distribution stage)
|
||||
// then put a timeout on that IP during which new submissions from them are dropped for a short
|
||||
// time.
|
||||
|
@ -833,7 +837,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
// message and close it.
|
||||
// If an outgoing connection - refuse reconnections via ZAP and just close it.
|
||||
|
||||
MDEBUG("Received a blink tx from " << (m.sn ? "SN " : "non-SN ") << as_hex(m.pubkey));
|
||||
MDEBUG("Received a blink tx from " << (m.conn.sn() ? "SN " : "non-SN ") << to_hex(m.conn.pubkey()));
|
||||
|
||||
auto keys = snw.core.get_service_node_keys();
|
||||
assert(keys);
|
||||
|
@ -844,7 +848,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
// No valid data and so no reply tag; we can't send a response
|
||||
return;
|
||||
}
|
||||
auto &data = boost::get<bt_dict>(m.data[0]);
|
||||
auto data = bt_deserialize<bt_dict>(m.data[0]);
|
||||
|
||||
auto tag = get_or<uint64_t>(data, "!", 0);
|
||||
|
||||
|
@ -852,7 +856,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
if (hf_version < HF_VERSION_BLINK) {
|
||||
MWARNING("Rejecting blink message: blink is not available for hardfork " << (int) hf_version);
|
||||
if (tag)
|
||||
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "Invalid blink authorization height"}});
|
||||
m.send_back("bl_nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "Invalid blink authorization height"_sv}}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -863,14 +867,14 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
if (blink_height < local_height - 2) {
|
||||
MINFO("Rejecting blink tx because blink auth height is too low (" << blink_height << " vs. " << local_height << ")");
|
||||
if (tag)
|
||||
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "Invalid blink authorization height"}});
|
||||
m.send_back("bl_nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "Invalid blink authorization height"_sv}}));
|
||||
return;
|
||||
} else if (blink_height > local_height + 2) {
|
||||
// TODO: if within some threshold (maybe 5-10?) we could hold it and process it once we are
|
||||
// within 2.
|
||||
MINFO("Rejecting blink tx because blink auth height is too high (" << blink_height << " vs. " << local_height << ")");
|
||||
if (tag)
|
||||
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "Invalid blink authorization height"}});
|
||||
m.send_back("bl_nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "Invalid blink authorization height"_sv}}));
|
||||
return;
|
||||
}
|
||||
MTRACE("Blink tx auth height " << blink_height << " is valid (local height is " << local_height << ")");
|
||||
|
@ -879,10 +883,10 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
if (t_it == data.end()) {
|
||||
MINFO("Rejecting blink tx: no tx data included in request");
|
||||
if (tag)
|
||||
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "No transaction included in blink request"}});
|
||||
m.send_back("bl_nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "No transaction included in blink request"_sv}}));
|
||||
return;
|
||||
}
|
||||
const std::string &tx_data = boost::get<std::string>(t_it->second);
|
||||
const std::string &tx_data = t_it->second.get<std::string>();
|
||||
MTRACE("Blink tx data is " << tx_data.size() << " bytes");
|
||||
|
||||
// "hash" is optional -- it lets us short-circuit processing the tx if we've already seen it,
|
||||
|
@ -890,7 +894,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
// the hash if we haven't seen it before -- this is only used to skip propagation and
|
||||
// validation.
|
||||
crypto::hash tx_hash;
|
||||
auto &tx_hash_str = boost::get<std::string>(data.at("#"));
|
||||
auto &tx_hash_str = data.at("#").get<std::string>();
|
||||
bool already_approved = false, already_rejected = false;
|
||||
if (tx_hash_str.size() == sizeof(crypto::hash)) {
|
||||
std::memcpy(tx_hash.data, tx_hash_str.data(), sizeof(crypto::hash));
|
||||
|
@ -915,7 +919,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
// the reply until a signature comes in that flips it to approved/rejected
|
||||
// status.
|
||||
it->second.reply_tag = tag;
|
||||
it->second.reply_pubkey = m.pubkey;
|
||||
it->second.reply_pubkey = m.conn.pubkey();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
@ -924,16 +928,16 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
}
|
||||
}
|
||||
}
|
||||
MTRACE("Blink tx hash: " << as_hex(tx_hash.data));
|
||||
MTRACE("Blink tx hash: " << to_hex(tx_hash.data));
|
||||
} else {
|
||||
MINFO("Rejecting blink tx: invalid tx hash included in request");
|
||||
if (tag)
|
||||
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "Invalid transaction hash"}});
|
||||
m.send_back("bl_nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "Invalid transaction hash"s}}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (already_approved || already_rejected) {
|
||||
snw.snn.send(m.pubkey, already_approved ? "bl_good" : "bl_bad", bt_dict{{"!", tag}}, send_option::optional{});
|
||||
m.send_back(already_approved ? "bl_good" : "bl_bad", bt_serialize(bt_dict{{"!", tag}}), send_option::optional{});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -944,12 +948,12 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
} catch (const std::runtime_error &e) {
|
||||
MINFO("Rejecting blink tx: " << e.what());
|
||||
if (tag)
|
||||
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "Unable to retrieve blink quorum: "s + e.what()}});
|
||||
m.send_back("bl_nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "Unable to retrieve blink quorum: "s + e.what()}}));
|
||||
return;
|
||||
}
|
||||
|
||||
peer_info pinfo{snw, quorum_type::blink, blink_quorums.begin(), blink_quorums.end(), true /*opportunistic*/,
|
||||
{snw.core.get_service_node_list().get_pubkey_from_x25519(x25519_from_string(m.pubkey))} // exclude the peer that just sent it to us
|
||||
{snw.core.get_service_node_list().get_pubkey_from_x25519(x25519_from_string(m.conn.pubkey()))} // exclude the peer that just sent it to us
|
||||
};
|
||||
|
||||
if (pinfo.my_position_count > 0)
|
||||
|
@ -957,7 +961,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
else {
|
||||
MINFO("Rejecting blink tx: this service node is not a member of the blink quorum!");
|
||||
if (tag)
|
||||
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "Blink tx relayed to non-blink quorum member"}});
|
||||
m.send_back("bl_nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "Blink tx relayed to non-blink quorum member"_sv}}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -974,7 +978,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash_actual)) {
|
||||
MINFO("Rejecting blink tx: failed to parse transaction data");
|
||||
if (tag)
|
||||
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "Failed to parse transaction data"}});
|
||||
m.send_back("bl_nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "Failed to parse transaction data"_sv}}));
|
||||
return;
|
||||
}
|
||||
MTRACE("Successfully parsed transaction data");
|
||||
|
@ -982,7 +986,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
if (tx_hash != tx_hash_actual) {
|
||||
MINFO("Rejecting blink tx: submitted tx hash " << tx_hash << " did not match actual tx hash " << tx_hash_actual);
|
||||
if (tag)
|
||||
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "Invalid transaction hash"}});
|
||||
m.send_back("bl_nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "Invalid transaction hash"_sv}}));
|
||||
return;
|
||||
} else {
|
||||
MTRACE("Pre-computed tx hash matches actual tx hash");
|
||||
|
@ -994,7 +998,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
if (!pinfo.strong_peers) {
|
||||
MWARNING("Could not find connection info for any blink quorum peers. Aborting blink tx");
|
||||
if (tag)
|
||||
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "No quorum peers are currently reachable"}});
|
||||
m.send_back("bl_nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "No quorum peers are currently reachable"_sv}}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1014,7 +1018,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
bl_info.pending_sigs.clear();
|
||||
if (tag > 0) {
|
||||
bl_info.reply_tag = tag;
|
||||
bl_info.reply_pubkey = m.pubkey;
|
||||
bl_info.reply_pubkey = m.conn.pubkey();
|
||||
}
|
||||
}
|
||||
MTRACE("Accepted new blink tx for verification");
|
||||
|
@ -1071,12 +1075,12 @@ void handle_blink(SNNetwork::message &m, void *self) {
|
|||
signatures.emplace_back(approved, qi, pinfo.my_position[qi], sig);
|
||||
}
|
||||
|
||||
process_blink_signatures(snw, btxptr, blink_quorums, checksum, std::move(signatures), tag, m.pubkey);
|
||||
process_blink_signatures(snw, btxptr, blink_quorums, checksum, std::move(signatures), tag, m.conn.pubkey());
|
||||
}
|
||||
|
||||
template <typename T, typename CopyValue>
|
||||
void copy_signature_values(std::list<pending_signature> &signatures, const bt_value &val, CopyValue copy_value) {
|
||||
auto &results = boost::get<bt_list>(val);
|
||||
auto &results = val.get<bt_list>();
|
||||
if (signatures.empty())
|
||||
signatures.resize(results.size());
|
||||
else if (results.empty())
|
||||
|
@ -1110,15 +1114,13 @@ void copy_signature_values(std::list<pending_signature> &signatures, const bt_va
|
|||
/// each list corresponds to the values at the same position of the other lists.
|
||||
///
|
||||
/// Signatures will be forwarded if new; known signatures will be ignored.
|
||||
void handle_blink_signature(SNNetwork::message &m, void *self) {
|
||||
auto &snw = SNNWrapper::from(self);
|
||||
|
||||
MDEBUG("Received a blink tx signature from SN " << as_hex(m.pubkey));
|
||||
void handle_blink_signature(Message& m, SNNWrapper& snw) {
|
||||
MDEBUG("Received a blink tx signature from SN " << to_hex(m.conn.pubkey()));
|
||||
|
||||
if (m.data.size() != 1)
|
||||
throw std::runtime_error("Rejecting blink signature: expected one data entry not " + std::to_string(m.data.size()));
|
||||
|
||||
auto &data = boost::get<bt_dict>(m.data[0]);
|
||||
auto data = bt_deserialize<bt_dict>(m.data[0]);
|
||||
|
||||
uint64_t blink_height = 0, checksum = 0;
|
||||
crypto::hash tx_hash;
|
||||
|
@ -1135,7 +1137,7 @@ void handle_blink_signature(SNNetwork::message &m, void *self) {
|
|||
blink_height = get_int<uint64_t>(val);
|
||||
break;
|
||||
case '#': {
|
||||
auto &hash_str = boost::get<std::string>(val);
|
||||
auto &hash_str = val.get<std::string>();
|
||||
if (hash_str.size() != sizeof(crypto::hash))
|
||||
throw std::invalid_argument("Invalid blink signature data: invalid tx hash");
|
||||
std::memcpy(tx_hash.data, hash_str.data(), sizeof(crypto::hash));
|
||||
|
@ -1168,7 +1170,7 @@ void handle_blink_signature(SNNetwork::message &m, void *self) {
|
|||
break;
|
||||
case 's':
|
||||
copy_signature_values<crypto::signature>(signatures, val, [](crypto::signature &dest, const bt_value &v) {
|
||||
auto &sig_str = boost::get<std::string>(v);
|
||||
auto& sig_str = v.get<std::string>();
|
||||
if (sig_str.size() != sizeof(crypto::signature))
|
||||
throw std::invalid_argument("Invalid blink signature data: invalid signature");
|
||||
std::memcpy(&dest, sig_str.data(), sizeof(crypto::signature));
|
||||
|
@ -1229,7 +1231,7 @@ void handle_blink_signature(SNNetwork::message &m, void *self) {
|
|||
|
||||
MINFO("Found blink tx in local blink cache");
|
||||
|
||||
process_blink_signatures(snw, btxptr, blink_quorums, checksum, std::move(signatures), reply_tag, reply_pubkey, m.pubkey);
|
||||
process_blink_signatures(snw, btxptr, blink_quorums, checksum, std::move(signatures), reply_tag, reply_pubkey, m.conn.pubkey());
|
||||
}
|
||||
|
||||
|
||||
|
@ -1317,7 +1319,7 @@ std::future<std::pair<cryptonote::blink_result, std::string>> send_blink(void *o
|
|||
return;
|
||||
}
|
||||
if (!proof.pubkey_x25519 || !proof.quorumnet_port || !proof.public_ip) {
|
||||
MTRACE("Not including node " << pubkey << ": missing x25519(" << as_hex(get_data_as_string(proof.pubkey_x25519)) << "), "
|
||||
MTRACE("Not including node " << pubkey << ": missing x25519(" << to_hex(get_data_as_string(proof.pubkey_x25519)) << "), "
|
||||
"public_ip(" << epee::string_tools::get_ip_string_from_int32(proof.public_ip) << "), or qnet port(" << proof.quorumnet_port << ")");
|
||||
return;
|
||||
}
|
||||
|
@ -1335,17 +1337,17 @@ std::future<std::pair<cryptonote::blink_result, std::string>> send_blink(void *o
|
|||
indices.resize(4);
|
||||
brd->remote_count = indices.size();
|
||||
|
||||
send_option::serialized data{bt_dict{
|
||||
std::string data = bt_serialize<bt_dict>({
|
||||
{"!", blink_tag},
|
||||
{"#", get_data_as_string(tx_hash)},
|
||||
{"h", height},
|
||||
{"q", checksum},
|
||||
{"t", tx_blob}
|
||||
}};
|
||||
});
|
||||
|
||||
for (size_t i : indices) {
|
||||
MINFO("Relaying blink tx to " << as_hex(remotes[i].first) << " @ " << remotes[i].second);
|
||||
snw.snn.send(remotes[i].first, "blink", data, send_option::hint{remotes[i].second});
|
||||
MINFO("Relaying blink tx to " << to_hex(remotes[i].first) << " @ " << remotes[i].second);
|
||||
snw.lmq.send(remotes[i].first, "blink", data, send_option::hint{remotes[i].second});
|
||||
}
|
||||
} catch (...) {
|
||||
auto lock = tools::unique_lock(pending_blink_result_mutex);
|
||||
|
@ -1404,14 +1406,14 @@ void common_blink_response(uint64_t tag, cryptonote::blink_result res, std::stri
|
|||
///
|
||||
/// It's possible for some nodes to accept and others to refuse, so we don't actually set the
|
||||
/// promise unless we get a nostart response from a majority of the remotes.
|
||||
void handle_blink_not_started(SNNetwork::message &m, void *) {
|
||||
void handle_blink_not_started(Message& m) {
|
||||
if (m.data.size() != 1) {
|
||||
MERROR("Bad blink not started response: expected one data entry not " << m.data.size());
|
||||
return;
|
||||
}
|
||||
auto &data = boost::get<bt_dict>(m.data[0]);
|
||||
auto data = bt_deserialize<bt_dict>(m.data[0]);
|
||||
auto tag = get_int<uint64_t>(data.at("!"));
|
||||
auto &error = boost::get<std::string>(data.at("e"));
|
||||
auto& error = data.at("e").get<std::string>();
|
||||
|
||||
MINFO("Received no-start blink response: " << error);
|
||||
|
||||
|
@ -1423,12 +1425,12 @@ void handle_blink_not_started(SNNetwork::message &m, void *) {
|
|||
///
|
||||
/// ! - the tag as included in the submission
|
||||
///
|
||||
void handle_blink_failure(SNNetwork::message &m, void *) {
|
||||
void handle_blink_failure(Message &m) {
|
||||
if (m.data.size() != 1) {
|
||||
MERROR("Blink failure message not understood: expected one data entry not " << m.data.size());
|
||||
return;
|
||||
}
|
||||
auto &data = boost::get<bt_dict>(m.data[0]);
|
||||
auto data = bt_deserialize<bt_dict>(m.data[0]);
|
||||
auto tag = get_int<uint64_t>(data.at("!"));
|
||||
|
||||
// TODO - we ought to be able to signal an error message *sometimes*, e.g. if one of the remotes
|
||||
|
@ -1447,12 +1449,12 @@ void handle_blink_failure(SNNetwork::message &m, void *) {
|
|||
///
|
||||
/// ! - the tag as included in the submission
|
||||
///
|
||||
void handle_blink_success(SNNetwork::message &m, void *) {
|
||||
void handle_blink_success(Message& m) {
|
||||
if (m.data.size() != 1) {
|
||||
MERROR("Blink success message not understood: expected one data entry not " << m.data.size());
|
||||
return;
|
||||
}
|
||||
auto &data = boost::get<bt_dict>(m.data[0]);
|
||||
auto data = bt_deserialize<bt_dict>(m.data[0]);
|
||||
auto tag = get_int<uint64_t>(data.at("!"));
|
||||
|
||||
MINFO("Received blink success response");
|
||||
|
@ -1460,19 +1462,19 @@ void handle_blink_success(SNNetwork::message &m, void *) {
|
|||
common_blink_response(tag, cryptonote::blink_result::accepted, ""s);
|
||||
}
|
||||
|
||||
void handle_ping(SNNetwork::message &m, void *) {
|
||||
void handle_ping(Message& m) {
|
||||
uint64_t tag = 0;
|
||||
if (!m.data.empty()) {
|
||||
auto &data = boost::get<bt_dict>(m.data[0]);
|
||||
auto data = bt_deserialize<bt_dict>(m.data[0]);
|
||||
tag = get_or<uint64_t>(data, "!", 0);
|
||||
}
|
||||
|
||||
MINFO("Received ping request from " << (m.sn ? "SN" : "non-SN") << " " << as_hex(m.pubkey) << ", sending pong");
|
||||
m.reply("pong", bt_dict{{"!", tag}, {"sn", m.sn}});
|
||||
MINFO("Received ping request from " << (m.conn.sn() ? "SN" : "non-SN") << " " << to_hex(m.conn.pubkey()) << ", sending pong");
|
||||
m.send_back("ping.pong", bt_serialize(bt_dict{{"!", tag}, {"sn", m.conn.sn()}}));
|
||||
}
|
||||
|
||||
void handle_pong(SNNetwork::message &m, void *) {
|
||||
MINFO("Received pong from " << (m.sn ? "SN" : "non-SN") << " " << as_hex(m.pubkey));
|
||||
void handle_pong(Message& m) {
|
||||
MINFO("Received pong from " << (m.conn.sn() ? "SN" : "non-SN") << " " << to_hex(m.conn.pubkey()));
|
||||
}
|
||||
|
||||
} // end empty namespace
|
||||
|
@ -1485,36 +1487,65 @@ void init_core_callbacks() {
|
|||
cryptonote::quorumnet_delete = delete_snnwrapper;
|
||||
cryptonote::quorumnet_relay_obligation_votes = relay_obligation_votes;
|
||||
cryptonote::quorumnet_send_blink = send_blink;
|
||||
}
|
||||
|
||||
// Receives an obligation vote
|
||||
SNNetwork::register_command("vote_ob", SNNetwork::command_type::quorum, handle_obligation_vote);
|
||||
namespace {
|
||||
void setup_endpoints(SNNWrapper& snw) {
|
||||
auto& lmq = snw.lmq;
|
||||
|
||||
// Receives a new blink tx submission from an external node, or forward from other quorum
|
||||
// members who received it from an external node.
|
||||
SNNetwork::register_command("blink", SNNetwork::command_type::public_, handle_blink);
|
||||
// quorum.*: commands between quorum members, requires that both side of the connection is a SN
|
||||
lmq.add_category("quorum", Access{AuthLevel::none, true /*remote sn*/, true /*local sn*/}, 2 /*reserved threads*/)
|
||||
// Receives an obligation vote
|
||||
.add_command("vote_ob", [&snw](Message& m) { handle_obligation_vote(m, snw); })
|
||||
// Receives blink tx signatures or rejections between quorum members (either original or
|
||||
// forwarded). These are propagated by the receiver if new
|
||||
.add_command("blink_sign", [&snw](Message& m) { handle_blink_signature(m, snw); })
|
||||
;
|
||||
|
||||
// Sends a message back to the blink initiator that the transaction was NOT relayed, either
|
||||
// because the height was invalid or the quorum checksum failed. This is only sent by the entry
|
||||
// point service nodes into the quorum to let it know the tx verification has not started from
|
||||
// that node. It does not necessarily indicate a failure unless all entry point attempts return
|
||||
// the same.
|
||||
SNNetwork::register_command("bl_nostart", SNNetwork::command_type::response, handle_blink_not_started);
|
||||
// blink.*: commands sent to blink quorum members from anyone (e.g. blink submission)
|
||||
lmq.add_category("blink", Access{AuthLevel::none, false /*remote sn*/, true /*local sn*/}, 1 /*reserved thread*/)
|
||||
// Receives a new blink tx submission from an external node, or forward from other quorum
|
||||
// members who received it from an external node.
|
||||
.add_command("submit", [&snw](Message& m) { handle_blink(m, snw); })
|
||||
;
|
||||
|
||||
// Sends a message from the entry SNs back to the initiator that the Blink tx has been rejected:
|
||||
// that is, enough signed rejections have occured that the Blink tx cannot be accepted.
|
||||
SNNetwork::register_command("bl_bad", SNNetwork::command_type::response, handle_blink_failure);
|
||||
// bl.*: responses to blinks sent from quorum members back to the node who submitted the blink
|
||||
lmq.add_category("bl", Access{AuthLevel::none, true /*remote sn*/, false /*local sn*/})
|
||||
// Message sent back to the blink initiator that the transaction was NOT relayed, either
|
||||
// because the height was invalid or the quorum checksum failed. This is only sent by the
|
||||
// entry point service nodes into the quorum to let it know the tx verification has not
|
||||
// started from that node. It does not necessarily indicate a failure unless all entry
|
||||
// point attempts return the same.
|
||||
.add_command("nostart", handle_blink_not_started)
|
||||
// Message send back from the entry SNs back to the initiator that the Blink tx has been
|
||||
// rejected: that is, enough signed rejections have occured that the Blink tx cannot be
|
||||
// accepted.
|
||||
.add_command("bad", handle_blink_failure)
|
||||
// Sends a message from the entry SNs back to the initiator that the Blink tx has been
|
||||
// accepted and validated and is being broadcast to the network.
|
||||
.add_command("good", handle_blink_success)
|
||||
;
|
||||
|
||||
// Sends a message from the entry SNs back to the initiator that the Blink tx has been accepted
|
||||
// and validated and is being broadcast to the network.
|
||||
SNNetwork::register_command("bl_good", SNNetwork::command_type::response, handle_blink_success);
|
||||
// ping.ping, ping.pong: triggers a reply with the auth status, used for quorumnet connectivity
|
||||
// testing.
|
||||
lmq.add_category("ping", Access{AuthLevel::none})
|
||||
.add_command("ping", handle_ping)
|
||||
.add_command("pong", handle_pong)
|
||||
;
|
||||
|
||||
// Receives blink tx signatures or rejections between quorum members (either original or
|
||||
// forwarded). These are propagated by the receiver if new
|
||||
SNNetwork::register_command("blink_sign", SNNetwork::command_type::quorum, handle_blink_signature);
|
||||
// Compatibility aliases. Transition plan:
|
||||
// 7.x: keep the aliases and use them, since we need 6.x nodes to understand
|
||||
// 8.x: keep the aliases (so the 7.x nodes still using them can talk to 8.x), but don't use them
|
||||
// anymore.
|
||||
// 8.x.1 (i.e. the first release after the 8.x hard fork): remove the aliases
|
||||
|
||||
// Replies with a pong and the auth status, used for quorumnet connectivity testing.
|
||||
SNNetwork::register_command("ping", SNNetwork::command_type::public_, handle_ping);
|
||||
SNNetwork::register_command("pong", SNNetwork::command_type::public_, handle_pong);
|
||||
lmq.add_command_alias("vote_ob", "quorum.vote_ob");
|
||||
lmq.add_command_alias("blink_sign", "quorum.blink_sign");
|
||||
lmq.add_command_alias("blink", "blink.submit");
|
||||
lmq.add_command_alias("bl_nostart", "bl.nostart");
|
||||
lmq.add_command_alias("bl_bad", "bl.bad");
|
||||
lmq.add_command_alias("bl_good", "bl.good");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2019, The Loki Project
|
||||
// Copyright (c) 2019-2020, The Loki Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
|
@ -28,11 +28,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
// This file (plus .cpp) contains the glue layer between cryptonote_core and quorumnet.
|
||||
// This file (plus .cpp) contains the glue layer between cryptonote_core and loki-mq.
|
||||
|
||||
#include <vector>
|
||||
//#include "quorumnet/service_node.h"
|
||||
//
|
||||
|
||||
namespace service_nodes {
|
||||
struct quorum_vote_t;
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
# Copyright (c) 2019, The Loki 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(quorumnet
|
||||
sn_network.cpp
|
||||
bt_serialize.cpp
|
||||
)
|
||||
target_link_libraries(quorumnet PRIVATE ${ZMQ_LIBRARIES})
|
|
@ -1,145 +0,0 @@
|
|||
// Copyright (c) 2019, The Loki 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 "bt_serialize.h"
|
||||
|
||||
namespace quorumnet {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template void bt_expect<bt_deserialize_invalid>(std::istream &is, char expect);
|
||||
template void bt_expect<bt_deserialize_invalid_type>(std::istream &is, char expect);
|
||||
|
||||
void bt_deserialize<std::string>::operator()(std::istream &is, std::string &val) {
|
||||
char first = is.peek();
|
||||
bt_no_eof(is);
|
||||
if (first < '0' || first > '9')
|
||||
throw bt_deserialize_invalid_type("Expected 0-9 but found '" + std::string(1, first) + "' at input location " + std::to_string(is.tellg()));
|
||||
uint64_t len;
|
||||
is >> len;
|
||||
char colon = is.peek();
|
||||
if (colon == ':')
|
||||
is.ignore();
|
||||
else
|
||||
throw bt_deserialize_invalid("Expected : but found '" + std::string(1, colon) + "' at input location " + std::to_string(is.tellg()));
|
||||
|
||||
if (len <= 4096) {
|
||||
val.clear();
|
||||
val.resize(len);
|
||||
is.read(&val[0], len);
|
||||
bt_no_eof(is);
|
||||
return;
|
||||
}
|
||||
// Otherwise the serialization contains a large (>4K) value: don't trust the caller by
|
||||
// pre-reserving (so that they can't run us out of memory by sending a malformed fake huge value
|
||||
// to deserialize). Instead just let `val` reallocate as std::string sees fit.
|
||||
val.clear();
|
||||
char buffer[4096];
|
||||
while (len) {
|
||||
auto read = std::min<uint64_t>(len, 4096);
|
||||
is.read(buffer, read);
|
||||
bt_no_eof(is);
|
||||
val.append(buffer, read);
|
||||
len -= read;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we are on a 2's complement architecture. It's highly unlikely that this code ever
|
||||
// runs on a non-2s-complement architecture, but check at compile time because we rely on these
|
||||
// relations below.
|
||||
static_assert(std::numeric_limits<int64_t>::min() + std::numeric_limits<int64_t>::max() == -1 &&
|
||||
static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + uint64_t{1} == (uint64_t{1} << 63),
|
||||
"Non 2s-complement architecture not supported!");
|
||||
|
||||
std::pair<maybe_signed_int64_t, bool> bt_deserialize_integer(std::istream &is) {
|
||||
bt_expect<bt_deserialize_invalid_type>(is, 'i');
|
||||
std::pair<maybe_signed_int64_t, bool> result;
|
||||
char first = is.peek();
|
||||
if (first == '-') {
|
||||
result.second = true;
|
||||
is.ignore();
|
||||
first = is.peek();
|
||||
}
|
||||
bt_no_eof(is);
|
||||
if (first < '0' || first > '9')
|
||||
throw bt_deserialize_invalid("Expected 0-9 but found '" + std::string(1, first) + "' at input location " + std::to_string(is.tellg()));
|
||||
|
||||
uint64_t uval;
|
||||
is >> uval;
|
||||
bt_expect(is, 'e');
|
||||
if (result.second) {
|
||||
if (uval > (uint64_t{1} << 63))
|
||||
throw bt_deserialize_invalid("Found too-large negative value just before input location " + std::to_string(is.tellg()));
|
||||
result.first.i64 = -uval;
|
||||
}
|
||||
else {
|
||||
result.first.u64 = uval;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template struct bt_deserialize<int64_t>;
|
||||
template struct bt_deserialize<uint64_t>;
|
||||
|
||||
void bt_deserialize<bt_value, void>::operator()(std::istream &is, bt_value &val) {
|
||||
auto next = is.peek();
|
||||
bt_no_eof(is);
|
||||
switch (next) {
|
||||
case 'd': {
|
||||
using dict_t = std::unordered_map<std::string, bt_value>;
|
||||
dict_t dict;
|
||||
bt_deserialize<dict_t>{}(is, dict);
|
||||
val = std::move(dict);
|
||||
break;
|
||||
}
|
||||
case 'l': {
|
||||
using list_t = std::list<bt_value>;
|
||||
list_t list;
|
||||
bt_deserialize<list_t>{}(is, list);
|
||||
val = std::move(list);
|
||||
break;
|
||||
}
|
||||
case 'i': {
|
||||
auto read = bt_deserialize_integer(is);
|
||||
val = read.first.i64; // We only store an i64, but can get a u64 out of it via get<uint64_t>(val)
|
||||
break;
|
||||
}
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9': {
|
||||
std::string str;
|
||||
bt_deserialize<std::string>{}(is, str);
|
||||
val = std::move(str);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw bt_deserialize_invalid("Deserialize failed: encountered invalid value '" + std::string(1, next) + "'; expected one of [0-9idl]");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace quorumnet
|
|
@ -1,596 +0,0 @@
|
|||
// Copyright (c) 2019, The Loki 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
|
||||
|
||||
// Please no epee. *Especially* for serialization!
|
||||
#include <string>
|
||||
#ifdef __cpp_lib_string_view
|
||||
#include <string_view>
|
||||
#endif
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <ostream>
|
||||
#include <istream>
|
||||
#include <sstream>
|
||||
#include <boost/variant.hpp>
|
||||
#include "common.h"
|
||||
#include "../common/osrb.h"
|
||||
|
||||
|
||||
namespace quorumnet {
|
||||
|
||||
/** \file
|
||||
* Quorumnet serialization is very simple: we support two primitive types, strings and integers,
|
||||
* and two container types, lists and dicts with string keys. On the wire these go in BitTorrent
|
||||
* byte encoding as described in BEP-0003 (https://www.bittorrent.org/beps/bep_0003.html#bencoding).
|
||||
*
|
||||
* On the C++ side, on input we allow strings, integral types, STL-like containers of these types,
|
||||
* and STL-like containers of pairs with a string first value and any of these types as second
|
||||
* value. We also accept boost::variants and std::variants (if compiled with std::variant support,
|
||||
* i.e. in C++17 mode) that contain any of these.
|
||||
*
|
||||
* One minor deviation from BEP-0003 is that we don't support serializing values that don't fit in a
|
||||
* 64-bit integer.
|
||||
*
|
||||
* On deserialization we can either deserialize into a boost::variant that supports everything, or
|
||||
* we can fill a container of your given type (though this fails if the container isn't compatible
|
||||
* with the deserialized data).
|
||||
*/
|
||||
|
||||
/// Exception throw if deserialization fails
|
||||
class bt_deserialize_invalid : public std::invalid_argument {
|
||||
using std::invalid_argument::invalid_argument;
|
||||
};
|
||||
|
||||
/// A more specific subclass that is thown if the serialization type is an initial mismatch: for
|
||||
/// example, trying deserializing an int but the next thing in input is a list. This is not,
|
||||
/// however, thrown if the type initially looks fine but, say, a nested serialization fails. This
|
||||
/// error will only be thrown when the input stream has not been advanced (and so can be tried for a
|
||||
/// different type).
|
||||
class bt_deserialize_invalid_type : public bt_deserialize_invalid {
|
||||
using bt_deserialize_invalid::bt_deserialize_invalid;
|
||||
};
|
||||
|
||||
/// Recursive generic type that can store everything valid for a BT serialization.
|
||||
using bt_value = boost::make_recursive_variant<
|
||||
std::string, int64_t,
|
||||
std::list<boost::recursive_variant_>,
|
||||
std::unordered_map<std::string, boost::recursive_variant_>
|
||||
>::type;
|
||||
|
||||
/// Convenience type that holds a dict of generic values (though *any* compatible data type can be used).
|
||||
using bt_dict = std::unordered_map<std::string, bt_value>;
|
||||
|
||||
/// Convenience type that holds a list of generic values (though *any* compatible data type can be used).
|
||||
using bt_list = std::list<bt_value>;
|
||||
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Fallback base case; we only get here if none of the partial specializations below work
|
||||
template <typename T, typename SFINAE = void>
|
||||
struct bt_serialize { static_assert(!std::is_same<T, T>::value, "Cannot serialize T: unsupported type for bt serialization"); };
|
||||
|
||||
template <typename T, typename SFINAE = void>
|
||||
struct bt_deserialize { static_assert(!std::is_same<T, T>::value, "Cannot deserialize T: unsupported type for bt deserialization"); };
|
||||
|
||||
/// Checks that we aren't at EOF (or other !good() status) and throws if we are.
|
||||
inline void bt_no_eof(std::istream &is) {
|
||||
if (!is.good())
|
||||
throw bt_deserialize_invalid(
|
||||
std::string(is.eof() ? "Unexpected EOF" : "I/O error") + " while deserializing at location " +
|
||||
std::to_string(is.tellg()));
|
||||
}
|
||||
|
||||
/// Checks that the type code is the next character, and if so, consumes it. If not, throws.
|
||||
template <typename Exception = bt_deserialize_invalid>
|
||||
void bt_expect(std::istream &is, char expect) {
|
||||
char t;
|
||||
is.get(t);
|
||||
bt_no_eof(is);
|
||||
if (t != expect) {
|
||||
is.unget();
|
||||
throw Exception("Expected '" + std::string(1, expect) + "' but found '" + std::string(1, t) + "' at input location " + std::to_string(is.tellg()));
|
||||
}
|
||||
}
|
||||
|
||||
extern template void bt_expect<bt_deserialize_invalid>(std::istream &is, char expect);
|
||||
extern template void bt_expect<bt_deserialize_invalid_type>(std::istream &is, char expect);
|
||||
|
||||
union maybe_signed_int64_t { int64_t i64; uint64_t u64; };
|
||||
|
||||
/// Deserializes a signed or unsigned 64. Sets the second bool to true iff the value is int64_t
|
||||
/// because a negative value was read. Throws an exception if the read value doesn't fit in a
|
||||
/// int64_t (if negative) or a uint64_t (if positive).
|
||||
std::pair<maybe_signed_int64_t, bool> bt_deserialize_integer(std::istream &is);
|
||||
|
||||
/// Integer specializations
|
||||
template <typename T>
|
||||
struct bt_serialize<T, std::enable_if_t<std::is_integral<T>::value>> {
|
||||
static_assert(sizeof(T) <= sizeof(uint64_t), "Serialization of integers larger than uint64_t is not supported");
|
||||
void operator()(std::ostream &os, const T &val) {
|
||||
// Cast 1-byte types to a larger type to avoid iostream interpreting them as single characters
|
||||
using output_type = std::conditional_t<(sizeof(T) > 1), T, std::conditional_t<std::is_signed<T>::value, int, unsigned>>;
|
||||
os << 'i' << static_cast<output_type>(val) << 'e';
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct bt_deserialize<T, std::enable_if_t<std::is_integral<T>::value>> {
|
||||
void operator()(std::istream &is, T &val) {
|
||||
constexpr uint64_t umax = static_cast<uint64_t>(std::numeric_limits<T>::max());
|
||||
constexpr int64_t smin = static_cast<int64_t>(std::numeric_limits<T>::min()),
|
||||
smax = static_cast<int64_t>(std::numeric_limits<T>::max());
|
||||
|
||||
auto read = bt_deserialize_integer(is);
|
||||
if (std::is_signed<T>::value) {
|
||||
if (!read.second) { // read a positive value
|
||||
if (read.first.u64 > umax)
|
||||
throw bt_deserialize_invalid("Found too-large value " + std::to_string(read.first.u64) + " when deserializing just before input location " + std::to_string(is.tellg()));
|
||||
val = static_cast<T>(read.first.u64);
|
||||
} else {
|
||||
bool oob = read.first.i64 < smin;
|
||||
oob |= read.first.i64 > smax;
|
||||
if (sizeof(T) < sizeof(int64_t) && oob)
|
||||
throw bt_deserialize_invalid("Found out-of-range value " + std::to_string(read.first.i64) + " when deserializing just before input location " + std::to_string(is.tellg()));
|
||||
val = static_cast<T>(read.first.i64);
|
||||
}
|
||||
} else {
|
||||
if (read.second)
|
||||
throw bt_deserialize_invalid("Found invalid negative value " + std::to_string(read.first.i64) + " when deserializing just before input location " + std::to_string(is.tellg()));
|
||||
if (sizeof(T) < sizeof(uint64_t) && read.first.u64 > umax)
|
||||
throw bt_deserialize_invalid("Found too-large value " + std::to_string(read.first.u64) + " when deserializing just before input location " + std::to_string(is.tellg()));
|
||||
val = static_cast<T>(read.first.u64);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern template struct bt_deserialize<int64_t>;
|
||||
extern template struct bt_deserialize<uint64_t>;
|
||||
|
||||
/// String specialization
|
||||
template <>
|
||||
struct bt_serialize<std::string> {
|
||||
void operator()(std::ostream &os, const std::string &val) { os << val.size() << ':' << val; }
|
||||
};
|
||||
template <>
|
||||
struct bt_deserialize<std::string> {
|
||||
void operator()(std::istream &is, std::string &val);
|
||||
};
|
||||
|
||||
/// char * and string literals -- we allow serialization for convenience, but not deserialization
|
||||
template <>
|
||||
struct bt_serialize<char *> {
|
||||
void operator()(std::ostream &os, const char *str) { auto len = std::strlen(str); os << len << ':'; os.write(str, len); }
|
||||
};
|
||||
template <size_t N>
|
||||
struct bt_serialize<char[N]> {
|
||||
void operator()(std::ostream &os, const char *str) { os << N-1 << ':'; os.write(str, N-1); }
|
||||
};
|
||||
|
||||
/// Partial dict validity; we don't check the second type for serializability, that will be handled
|
||||
/// via the base case static_assert if invalid.
|
||||
template <typename T, typename = void> struct is_bt_input_dict_container : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_bt_input_dict_container<T, std::enable_if_t<
|
||||
std::is_same<std::string, std::remove_cv_t<typename T::value_type::first_type>>::value,
|
||||
void_t<typename T::const_iterator /* is const iterable */,
|
||||
typename T::value_type::second_type /* has a second type */>>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T, typename = void> struct is_bt_insertable : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_bt_insertable<T,
|
||||
void_t<decltype(std::declval<T>().insert(std::declval<T>().end(), std::declval<typename T::value_type>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T, typename = void> struct is_bt_output_dict_container : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_bt_output_dict_container<T, std::enable_if_t<
|
||||
std::is_same<std::string, std::remove_cv_t<typename T::value_type::first_type>>::value &&
|
||||
is_bt_insertable<T>::value,
|
||||
void_t<typename T::value_type::second_type /* has a second type */>>>
|
||||
: std::true_type {};
|
||||
|
||||
|
||||
/// Specialization for a dict-like container (such as an unordered_map). We accept anything for a
|
||||
/// dict that is const iterable over something that looks like a pair with std::string for first
|
||||
/// value type. The value (i.e. second element of the pair) also must be serializable.
|
||||
template <typename T>
|
||||
struct bt_serialize<T, std::enable_if_t<is_bt_input_dict_container<T>::value>> {
|
||||
using second_type = typename T::value_type::second_type;
|
||||
using ref_pair = std::reference_wrapper<const typename T::value_type>;
|
||||
void operator()(std::ostream &os, const T &dict) {
|
||||
os << 'd';
|
||||
std::vector<ref_pair> pairs;
|
||||
pairs.reserve(dict.size());
|
||||
for (const auto &pair : dict)
|
||||
pairs.emplace(pairs.end(), pair);
|
||||
std::sort(pairs.begin(), pairs.end(), [](ref_pair a, ref_pair b) { return a.get().first < b.get().first; });
|
||||
for (auto &ref : pairs) {
|
||||
bt_serialize<std::string>{}(os, ref.get().first);
|
||||
bt_serialize<second_type>{}(os, ref.get().second);
|
||||
}
|
||||
os << 'e';
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct bt_deserialize<T, std::enable_if_t<is_bt_output_dict_container<T>::value>> {
|
||||
using second_type = typename T::value_type::second_type;
|
||||
void operator()(std::istream &is, T &dict) {
|
||||
bt_expect<bt_deserialize_invalid_type>(is, 'd');
|
||||
dict.clear();
|
||||
bt_deserialize<std::string> key_deserializer;
|
||||
bt_deserialize<second_type> val_deserializer;
|
||||
while (is.peek() != 'e') {
|
||||
bt_no_eof(is);
|
||||
std::string key;
|
||||
second_type val;
|
||||
try {
|
||||
key_deserializer(is, key);
|
||||
val_deserializer(is, val);
|
||||
} catch (const bt_deserialize_invalid_type &e) {
|
||||
// Rethrow a sub-element invalid type as a regular error (because the type *was* a list)
|
||||
throw bt_deserialize_invalid(e.what());
|
||||
}
|
||||
dict.insert(dict.end(), typename T::value_type{std::move(key), std::move(val)});
|
||||
}
|
||||
bt_expect(is, 'e');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// Accept anything that looks iterable; value serialization validity isn't checked here (it fails
|
||||
/// via the base case static assert).
|
||||
template <typename T, typename = void> struct is_bt_input_list_container : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_bt_input_list_container<T, std::enable_if_t<
|
||||
!std::is_same<T, std::string>::value &&
|
||||
!is_bt_input_dict_container<T>::value,
|
||||
void_t<typename T::const_iterator, typename T::value_type>>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T, typename = void> struct is_bt_output_list_container : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_bt_output_list_container<T, std::enable_if_t<
|
||||
!std::is_same<T, std::string>::value &&
|
||||
!is_bt_output_dict_container<T>::value &&
|
||||
is_bt_insertable<T>::value>>
|
||||
: std::true_type {};
|
||||
|
||||
|
||||
/// List specialization
|
||||
template <typename T>
|
||||
struct bt_serialize<T, std::enable_if_t<is_bt_input_list_container<T>::value>> {
|
||||
void operator()(std::ostream &os, const T &list) {
|
||||
os << 'l';
|
||||
for (const auto &v : list)
|
||||
bt_serialize<std::remove_cv_t<typename T::value_type>>{}(os, v);
|
||||
os << 'e';
|
||||
}
|
||||
};
|
||||
template <typename T>
|
||||
struct bt_deserialize<T, std::enable_if_t<is_bt_output_list_container<T>::value>> {
|
||||
using value_type = typename T::value_type;
|
||||
void operator()(std::istream &is, T &list) {
|
||||
bt_expect<bt_deserialize_invalid_type>(is, 'l');
|
||||
list.clear();
|
||||
bt_deserialize<value_type> deserializer;
|
||||
while (is.peek() != 'e') {
|
||||
bt_no_eof(is);
|
||||
value_type v;
|
||||
try {
|
||||
deserializer(is, v);
|
||||
} catch (const bt_deserialize_invalid_type &e) {
|
||||
// Rethrow a sub-element invalid type as a regular error (because the type *was* a list)
|
||||
throw bt_deserialize_invalid(e.what());
|
||||
}
|
||||
list.insert(list.end(), std::move(v));
|
||||
}
|
||||
bt_expect(is, 'e');
|
||||
}
|
||||
};
|
||||
|
||||
/// variant visitor; serializes whatever is contained
|
||||
class bt_serialize_visitor {
|
||||
std::ostream &os;
|
||||
public:
|
||||
using result_type = void;
|
||||
bt_serialize_visitor(std::ostream &os) : os{os} {}
|
||||
template <typename T> void operator()(const T &val) const {
|
||||
bt_serialize<T>{}(os, val);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using is_bt_deserializable = std::integral_constant<bool,
|
||||
std::is_same<T, std::string>::value || std::is_integral<T>::value ||
|
||||
is_bt_output_dict_container<T>::value || is_bt_output_list_container<T>::value>;
|
||||
|
||||
template <typename SFINAE, typename Variant, typename... Ts>
|
||||
struct bt_deserialize_try_variant_impl {
|
||||
void operator()(std::istream &, Variant &) {
|
||||
throw bt_deserialize_invalid("Deserialization failed: could not deserialize value into any variant type");
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Ts, typename Variant>
|
||||
void bt_deserialize_try_variant(std::istream &is, Variant &variant) {
|
||||
bt_deserialize_try_variant_impl<void, Variant, Ts...>{}(is, variant);
|
||||
}
|
||||
|
||||
|
||||
template <typename Variant, typename T, typename... Ts>
|
||||
struct bt_deserialize_try_variant_impl<std::enable_if_t<is_bt_deserializable<T>::value>, Variant, T, Ts...> {
|
||||
void operator()(std::istream &is, Variant &variant) {
|
||||
// Try to load the T variant. If deserialization fails with a invalid_type error then we
|
||||
// can try to next one (that error leaves the istream in the same state).
|
||||
try {
|
||||
T val;
|
||||
bt_deserialize<T>{}(is, val);
|
||||
variant = std::move(val);
|
||||
}
|
||||
catch (bt_deserialize_invalid_type &e) {
|
||||
bt_deserialize_try_variant<Ts...>(is, variant);
|
||||
}
|
||||
// Don't catch other exceptions: they aren't retriable failures
|
||||
}
|
||||
};
|
||||
template <typename Variant, typename T, typename... Ts>
|
||||
struct bt_deserialize_try_variant_impl<std::enable_if_t<!is_bt_deserializable<T>::value>, Variant, T, Ts...> {
|
||||
void operator()(std::istream &is, Variant &variant) {
|
||||
// Unsupported deserialization type, skip ahead
|
||||
bt_deserialize_try_variant<Ts...>(is, variant);
|
||||
}
|
||||
};
|
||||
|
||||
/// Serialize a boost::variant
|
||||
template <typename... Ts>
|
||||
struct bt_serialize<boost::variant<Ts...>> {
|
||||
void operator()(std::ostream &os, const boost::variant<Ts...> &val) {
|
||||
boost::apply_visitor(bt_serialize_visitor{os}, val);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct bt_deserialize<boost::variant<Ts...>> {
|
||||
void operator()(std::istream &is, boost::variant<Ts...> &val) {
|
||||
bt_deserialize_try_variant<Ts...>(is, val);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct bt_deserialize<bt_value, void> {
|
||||
void operator()(std::istream &is, bt_value &val);
|
||||
};
|
||||
|
||||
#ifdef __cpp_lib_variant
|
||||
/// C++17 std::variant support
|
||||
template <typename... Ts>
|
||||
struct bt_serialize<std::variant<Ts...>> {
|
||||
void operator()(std::ostream &os, const std::variant<Ts...> &val) {
|
||||
std::visit(bt_serialize_visitor{os}, val);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct bt_deserialize<std::variant<Ts...>> {
|
||||
void operator()(std::istream &is, std::variant<Ts...> &val) {
|
||||
bt_deserialize_try_variant<Ts...>(is, val);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
struct bt_stream_serializer {
|
||||
const T &val;
|
||||
explicit bt_stream_serializer(const T &val) : val{val} {}
|
||||
operator std::string() const {
|
||||
std::ostringstream oss;
|
||||
oss << *this;
|
||||
return oss.str();
|
||||
}
|
||||
};
|
||||
template <typename T>
|
||||
std::ostream &operator<<(std::ostream &os, const bt_stream_serializer<T> &s) {
|
||||
bt_serialize<T>{}(os, s.val);
|
||||
return os;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct bt_stream_deserializer {
|
||||
T &val;
|
||||
bt_stream_deserializer(T &val) : val{val} {}
|
||||
};
|
||||
template <typename T>
|
||||
std::istream &operator>>(std::istream &is, bt_stream_deserializer<T> &s) {
|
||||
bt_deserialize<T>{}(is, s.val);
|
||||
return is;
|
||||
};
|
||||
template <typename T>
|
||||
std::istream &operator>>(std::istream &is, bt_stream_deserializer<T> &&s) {
|
||||
bt_deserialize<T>{}(is, s.val);
|
||||
return is;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
|
||||
/// Returns a wrapper around a value reference that can serialize the value directly to an output
|
||||
/// stream or return it as a string. This class is intended to be used inline (i.e. without being
|
||||
/// stored) as in:
|
||||
///
|
||||
/// int number = 42;
|
||||
/// std::string encoded = bt_serialize(number);
|
||||
/// // Equivalent:
|
||||
/// //auto encoded = (std::string) bt_serialize(number);
|
||||
///
|
||||
/// std::list<int> my_list{{1,2,3}};
|
||||
/// std::cout << bt_serialize(my_list);
|
||||
///
|
||||
/// While it is possible to store the returned object and use it, such as:
|
||||
///
|
||||
/// auto encoded = bt_serialize(42);
|
||||
/// std::cout << encoded;
|
||||
///
|
||||
/// this approach is not generally recommended: the returned object stores a reference to the
|
||||
/// passed-in type, which may not survive. If doing this note that it is the caller's
|
||||
/// responsibility to ensure the serializer is not used past the end of the lifetime of the value
|
||||
/// being serialized.
|
||||
///
|
||||
/// Also note that serializing directly to an output stream is more efficient as no intermediate
|
||||
/// string containing the entire serialization has to be constructed.
|
||||
///
|
||||
template <typename T>
|
||||
detail::bt_stream_serializer<T> bt_serializer(const T &val) { return detail::bt_stream_serializer<T>{val}; }
|
||||
|
||||
/// Serializes into a std::string. This is exactly equivalant to casting the above to a std::string.
|
||||
template <typename T>
|
||||
std::string bt_serialize(const T &val) { return bt_serializer(val); }
|
||||
|
||||
/// Returns a wrapper around a value non-const reference so that you can deserialize from an input
|
||||
/// stream or a string using:
|
||||
///
|
||||
/// int value;
|
||||
/// is >> bt_deserializer(value);
|
||||
///
|
||||
template <typename T>
|
||||
detail::bt_stream_deserializer<T> bt_deserializer(T &val) { return detail::bt_stream_deserializer<T>{val}; }
|
||||
|
||||
/// Deserializes from a char * and size directly into `val`. Usage:
|
||||
///
|
||||
/// const char *encoded = "i42e";
|
||||
/// size_t n = 4;
|
||||
/// int value;
|
||||
/// bt_deserialize(encoded, n, value); // Sets value to 42
|
||||
template <typename T, std::enable_if_t<!std::is_const<T>::value, int> = 0>
|
||||
void bt_deserialize(const char *data, size_t len, T &val) {
|
||||
tools::one_shot_read_buffer buf{data, len};
|
||||
std::istream is{&buf};
|
||||
is >> bt_deserializer(val);
|
||||
}
|
||||
|
||||
/// Deserializes the given string directly into `val`. Usage:
|
||||
///
|
||||
/// std::string encoded = "i42e";
|
||||
/// int value;
|
||||
/// bt_deserialize(encoded, value); // Sets value to 42
|
||||
///
|
||||
template <typename T, std::enable_if_t<!std::is_const<T>::value, int> = 0>
|
||||
void bt_deserialize(
|
||||
#ifdef __cpp_lib_string_view
|
||||
std::string_view s,
|
||||
#else
|
||||
const std::string &s,
|
||||
#endif
|
||||
T &val) {
|
||||
return bt_deserialize(s.data(), s.size(), val);
|
||||
}
|
||||
|
||||
|
||||
/// Deserializes the given string into a `T`, which is returned.
|
||||
///
|
||||
/// std::string encoded = "li1ei2ei3ee"; // bt-encoded list of ints: [1,2,3]
|
||||
/// auto mylist = bt_deserialize<std::list<int>>(encoded);
|
||||
///
|
||||
template <typename T>
|
||||
T bt_deserialize(
|
||||
#ifdef __cpp_lib_string_view
|
||||
std::string_view s
|
||||
#else
|
||||
const std::string &s
|
||||
#endif
|
||||
) {
|
||||
T val;
|
||||
bt_deserialize(s, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/// Deserializes the given C-style string into a `T`, which is returned.
|
||||
///
|
||||
/// char *encoded = "li1ei2ei3ee"; // bt-encoded list of ints: [1,2,3]
|
||||
/// auto mylist = bt_deserialize_cstr<std::list<int>>(encoded, strlen(encoded));
|
||||
///
|
||||
template <typename T>
|
||||
T bt_deserialize_cstr(const char *data, size_t len) {
|
||||
T val;
|
||||
bt_deserialize(data, len, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/// Deserializes the given value into a generic `bt_value` boost::variant which is capable of
|
||||
/// holding all possible BT-encoded values.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// std::string encoded = "i42e";
|
||||
/// auto val = bt_get(encoded);
|
||||
/// int v = get_int<int>(val); // fails unless the encoded value was actually an integer that
|
||||
/// // fits into an `int`
|
||||
///
|
||||
inline bt_value bt_get(
|
||||
#ifdef __cpp_lib_string_view
|
||||
std::string_view s
|
||||
#else
|
||||
const std::string &s
|
||||
#endif
|
||||
) {
|
||||
return bt_deserialize<bt_value>(s);
|
||||
}
|
||||
|
||||
/// Helper functions to extract a value of some integral type from a bt_value which contains an
|
||||
/// integer. Does range checking, throwing std::overflow_error if the stored value is outside the
|
||||
/// range of the target type.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// std::string encoded = "i123456789e";
|
||||
/// auto val = bt_get(encoded);
|
||||
/// auto v = get_int<uint32_t>(val); // throws if the decoded value doesn't fit in a uint32_t
|
||||
template <typename IntType, std::enable_if_t<std::is_integral<IntType>::value, int> = 0>
|
||||
IntType get_int(const bt_value &v) {
|
||||
// It's highly unlikely that this code ever runs on a non-2s-complement architecture, but check
|
||||
// at compile time if converting to a uint64_t (because while int64_t -> uint64_t is
|
||||
// well-defined, uint64_t -> int64_t only does the right thing under 2's complement).
|
||||
static_assert(!std::is_unsigned<IntType>::value || sizeof(IntType) != sizeof(int64_t) || -1 == ~0,
|
||||
"Non 2s-complement architecture not supported!");
|
||||
int64_t value = boost::get<int64_t>(v);
|
||||
if (sizeof(IntType) < sizeof(int64_t)) {
|
||||
if (value > static_cast<int64_t>(std::numeric_limits<IntType>::max())
|
||||
|| value < static_cast<int64_t>(std::numeric_limits<IntType>::min()))
|
||||
throw std::overflow_error("Unable to extract integer value: stored value is outside the range of the requested type");
|
||||
}
|
||||
return static_cast<IntType>(value);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright (c) 2019, The Loki 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 <type_traits>
|
||||
|
||||
namespace quorumnet {
|
||||
|
||||
// Various utility templates
|
||||
|
||||
#ifdef __cpp_lib_void_t
|
||||
using std::void_t;
|
||||
#else
|
||||
/// C++17 void_t backport
|
||||
template <typename... Ts> struct void_t_impl { using type = void; };
|
||||
template <typename... Ts> using void_t = typename void_t_impl<Ts...>::type;
|
||||
#endif
|
||||
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -1,592 +0,0 @@
|
|||
// Copyright (c) 2019, The Loki 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
|
||||
|
||||
// Please no epee.
|
||||
#include "zmq.hpp"
|
||||
#include "bt_serialize.h"
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
namespace quorumnet {
|
||||
|
||||
/// Logging levels passed into LogFunc
|
||||
enum class LogLevel { trace, debug, info, warn, error, fatal };
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
/**
|
||||
* Class that represents a listening service node on the quorum p2p network. This object supports
|
||||
* connecting to service node peers and handles requests from node clients (for example, a remote
|
||||
* node submitting a Blink transaction to a Blink quorum).
|
||||
*
|
||||
* Internally the class uses a worker thread to handle messages from other service nodes, which it
|
||||
* then passed back into calling code via callbacks.
|
||||
*/
|
||||
class SNNetwork {
|
||||
public:
|
||||
/// The function type for looking up where to connect to the SN with the given pubkey. Should
|
||||
/// return an empty string for an invalid or unknown pubkey or one without a known address.
|
||||
using LookupFunc = std::function<std::string(const std::string &pubkey)>;
|
||||
|
||||
enum class allow { denied, client, service_node };
|
||||
/// Callback type invoked to determine whether the given ip and pubkey are allowed to connect to
|
||||
/// us as a SN, client, or not at all.
|
||||
///
|
||||
/// @param ip - the ip address of the incoming connection; will be empty if called to attempt to
|
||||
/// "upgrade" the permission of an existing non-SN connection.
|
||||
/// @param pubkey - the curve25519 pubkey (which is calculated from the SN ed25519 pubkey) of
|
||||
/// the connecting service node (32 byte string), or an empty string if this is a client
|
||||
/// connection without remote SN authentication.
|
||||
/// @returns an `allow` enum value: `denied` if the connection is not allowed, `client` if the
|
||||
/// connection is allowed as a client (i.e. not for SN-to-SN commands), `service_node` if the
|
||||
/// connection is a valid SN.
|
||||
using AllowFunc = std::function<allow(const std::string &ip, const std::string &pubkey)>;
|
||||
|
||||
/// Function pointer to ask whether a log of the given level is wanted. If it returns true the
|
||||
/// log message will be built and then passed to Log.
|
||||
using WantLog = bool (*)(LogLevel);
|
||||
///
|
||||
/// call to get somewhere to log to when there is a logging message. If it
|
||||
/// returns a std::ostream pointer then output is sent to it; otherwise (i.e. nullptr) output is
|
||||
/// suppressed. Takes three arguments: the log level, the __FILE__ value, and the __LINE__ value.
|
||||
using WriteLog = void (*)(LogLevel level, const char *file, int line, std::string msg);
|
||||
|
||||
/// Explicitly non-copyable, non-movable because most things here aren't copyable, and a few
|
||||
/// things aren't movable. If you need to pass the SNNetwork around, wrap it in a unique_ptr.
|
||||
SNNetwork(const SNNetwork &) = delete;
|
||||
SNNetwork &operator=(const SNNetwork &) = delete;
|
||||
SNNetwork(SNNetwork &&) = delete;
|
||||
SNNetwork &operator=(SNNetwork &&) = delete;
|
||||
|
||||
/// Encapsulates an incoming message from a remote node with message details plus extra info
|
||||
/// need to send a reply back through the proxy thread via the `reply()` method.
|
||||
class message {
|
||||
private:
|
||||
SNNetwork &net;
|
||||
public:
|
||||
std::string command; ///< The command name
|
||||
std::vector<bt_value> data; ///< The provided command data parts, if any.
|
||||
const std::string pubkey; ///< The originator pubkey (32 bytes)
|
||||
const bool sn; ///< True if the pubkey is from a SN, meaning we can/should reconnect to it if necessary
|
||||
|
||||
/// Constructor
|
||||
message(SNNetwork &net, std::string command, std::string pubkey, bool sn)
|
||||
: net{net}, command{std::move(command)}, pubkey{std::move(pubkey)}, sn{sn} {
|
||||
assert(this->pubkey.size() == 32);
|
||||
}
|
||||
|
||||
/// Sends a reply. Arguments are forward to send() but with send_option::optional{} added
|
||||
/// if the originator is not a SN. For SN messages (i.e. where `sn` is true) this is a
|
||||
/// "strong" reply by default in that the proxy will establish a new connection to the SN if
|
||||
/// no longer connected. For non-SN messages the reply will be attempted using the
|
||||
/// available routing information, but if the connection has already been closed the reply
|
||||
/// will be dropped.
|
||||
template <typename... Args>
|
||||
void reply(const std::string &command, Args &&...args);
|
||||
};
|
||||
|
||||
/// Opaque pointer sent to the callbacks, to allow for including arbitrary state data (for
|
||||
/// example, an owning object or state data). Defaults to nullptr and has to be explicitly set
|
||||
/// if desired.
|
||||
void *data = nullptr;
|
||||
|
||||
/// Possible command types; see register_command
|
||||
enum class command_type { quorum, public_, response };
|
||||
|
||||
private:
|
||||
zmq::context_t context;
|
||||
|
||||
/// A unique id for this SNNetwork, assigned in a thread-safe manner during construction.
|
||||
const int object_id;
|
||||
|
||||
/// The keypair of this SN, either provided or generated during construction
|
||||
std::string pubkey, privkey;
|
||||
|
||||
/// The thread in which most of the intermediate work happens (handling external connections
|
||||
/// and proxying requests between them to worker threads)
|
||||
std::thread proxy_thread;
|
||||
|
||||
/// Called to obtain a "command" socket that attaches to `control` to send commands to the
|
||||
/// proxy thread from other threads. This socket is unique per thread and SNNetwork instance.
|
||||
zmq::socket_t &get_control_socket();
|
||||
/// Stores all of the sockets created in different threads via `get_control_socket`. This is
|
||||
/// only used during destruction to close all of those open sockets, and is protected by an
|
||||
/// internal mutex which is only locked by new threads getting a control socket and the
|
||||
/// destructor.
|
||||
std::vector<std::shared_ptr<zmq::socket_t>> thread_control_sockets;
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
/// NB: The following are all the domain of the proxy thread (once it is started)!
|
||||
|
||||
/// The lookup function that tells us where to connect to a peer
|
||||
LookupFunc peer_lookup;
|
||||
/// Our listening socket for public connections, or null for a remote-only
|
||||
/// (TODO: change this to std::optional once we use C++17).
|
||||
std::unique_ptr<zmq::socket_t> listener;
|
||||
|
||||
/// Callback to see whether the incoming connection is allowed
|
||||
AllowFunc allow_connection;
|
||||
|
||||
/// Callback to see if we want a log message at the given level.
|
||||
WantLog want_logs;
|
||||
/// If want_logs returns true, the log message is build and passed into this.
|
||||
WriteLog logger;
|
||||
|
||||
/// Info about a peer's established connection to us. Note that "established" means both
|
||||
/// connected and authenticated.
|
||||
struct peer_info {
|
||||
/// True if we've authenticated this peer as a service node. (Note that new outgoing
|
||||
/// connections are *always* expected to go to service nodes and will update this to true).
|
||||
bool service_node = false;
|
||||
|
||||
/// Will be set to a non-empty routing prefix (which needs to be prefixed out outgoing
|
||||
/// messages) if we have (or at least recently had) an established incoming connection with
|
||||
/// this peer. Will be empty if there is no incoming connection.
|
||||
std::string incoming;
|
||||
|
||||
/// The index in `remotes` if we have an established outgoing connection to this peer, -1 if
|
||||
/// we have no outgoing connection to this peer.
|
||||
int outgoing = -1;
|
||||
|
||||
/// The last time we sent or received a message (or had some other relevant activity) with
|
||||
/// this peer. Used for closing outgoing connections that have reached an inactivity expiry
|
||||
/// time.
|
||||
std::chrono::steady_clock::time_point last_activity;
|
||||
|
||||
/// Updates last_activity to the current time
|
||||
void activity() { last_activity = std::chrono::steady_clock::now(); }
|
||||
|
||||
/// After more than this much activity we will close an idle connection
|
||||
std::chrono::milliseconds idle_expiry;
|
||||
};
|
||||
|
||||
struct pk_hash { size_t operator()(const std::string &pubkey) const { size_t h; std::memcpy(&h, pubkey.data(), sizeof(h)); return h; } };
|
||||
|
||||
/// Currently peer connections, pubkey -> peer_info
|
||||
std::unordered_map<std::string, peer_info, pk_hash> peers;
|
||||
|
||||
/// different polling sockets the proxy handler polls: this always contains some internal
|
||||
/// sockets for inter-thread communication followed by listener socket and a pollitem for every
|
||||
/// (outgoing) remote socket in `remotes`. This must be in a sequential vector because of zmq
|
||||
/// requirements (otherwise it would be far nicer to not have to synchronize the two vectors).
|
||||
std::vector<zmq::pollitem_t> pollitems;
|
||||
|
||||
/// Properly adds a socket to poll for input to pollitems
|
||||
void add_pollitem(zmq::socket_t &sock);
|
||||
|
||||
/// The number of internal sockets in `pollitems`
|
||||
static constexpr size_t poll_internal_size = 3;
|
||||
|
||||
/// The pollitems location corresponding to `remotes[0]`.
|
||||
const size_t poll_remote_offset; // will be poll_internal_size + 1 for a full listener (the +1 is the listening socket); poll_internal_size for a remote-only
|
||||
|
||||
/// The outgoing remote connections we currently have open along with the remote pubkeys. Note
|
||||
/// that the sockets here are generally accessed via the weak_ptr inside the `peers` element.
|
||||
/// Each element [i] here corresponds to an the pollitem_t at pollitems[i+1+poll_internal_size].
|
||||
/// (Ideally we'd use one structure, but zmq requires the pollitems be in contiguous storage).
|
||||
std::vector<std::pair<std::string, zmq::socket_t>> remotes;
|
||||
|
||||
/// Socket we listen on to receive control messages in the proxy thread. Each thread has its own
|
||||
/// internal "control" connection (returned by `get_control_socket()`) to this socket used to
|
||||
/// give instructions to the proxy such as instructing it to initiate a connection to a remote
|
||||
/// or send a message.
|
||||
zmq::socket_t command{context, zmq::socket_type::router};
|
||||
|
||||
/// Router socket to reach internal worker threads from proxy
|
||||
zmq::socket_t workers{context, zmq::socket_type::router};
|
||||
|
||||
/// Starts a new worker thread with the given id. Note that the worker may not yet be ready
|
||||
/// until a READY signal arrives on the worker socket.
|
||||
void spawn_worker(std::string worker_id);
|
||||
|
||||
/// Worker threads (ZMQ id -> thread)
|
||||
std::unordered_map<std::string, std::thread> worker_threads;
|
||||
|
||||
/// ZMQ ids of idle, active workers
|
||||
std::list<std::string> idle_workers;
|
||||
|
||||
/// Maximum number of worker threads created on demand up to this limit.
|
||||
unsigned int max_workers;
|
||||
|
||||
/// Worker thread loop
|
||||
void worker_thread(std::string worker_id);
|
||||
|
||||
/// Does the proxying work
|
||||
void proxy_loop(const std::vector<std::string> &bind);
|
||||
|
||||
/// Forwards an incoming message to an idle worker, removing the idle worker from the queue
|
||||
void proxy_to_worker(size_t conn_index, std::list<zmq::message_t> &&parts);
|
||||
|
||||
/// proxy thread command handlers for commands sent from the outer object QUIT. This doesn't
|
||||
/// get called immediately on a QUIT command: the QUIT commands tells workers to quit, then this
|
||||
/// gets called after all works have done so.
|
||||
void proxy_quit();
|
||||
|
||||
/// Common connection implementation used by proxy_connect/proxy_send. Returns the socket
|
||||
/// and, if a routing prefix is needed, the required prefix (or an empty string if not needed).
|
||||
/// For an optional connect that fail, returns nullptr for the socket.
|
||||
std::pair<zmq::socket_t *, std::string> proxy_connect(const std::string &pubkey, const std::string &connect_hint, bool optional, bool incoming_only, std::chrono::milliseconds keep_alive);
|
||||
|
||||
/// CONNECT command telling us to connect to a new pubkey. Returns the socket (which could be
|
||||
/// existing or a new one).
|
||||
std::pair<zmq::socket_t *, std::string> proxy_connect(bt_dict &&data);
|
||||
|
||||
/// DISCONNECT command telling us to disconnect out remote connection to the given pubkey (if we
|
||||
/// have one).
|
||||
void proxy_disconnect(const std::string &pubkey);
|
||||
|
||||
/// SEND command. Does a connect first, if necessary.
|
||||
void proxy_send(bt_dict &&data);
|
||||
|
||||
/// REPLY command. Like SEND, but only has a listening socket route to send back to and so is
|
||||
/// weaker (i.e. it cannot reconnect to the SN if the connection is no longer open).
|
||||
void proxy_reply(bt_dict &&data);
|
||||
|
||||
/// ZAP (https://rfc.zeromq.org/spec:27/ZAP/) authentication handler; this is called with the
|
||||
/// zap auth socket to do non-blocking processing of any waiting authentication requests waiting
|
||||
/// on it to verify whether the connection is from a valid/allowed SN.
|
||||
void process_zap_requests(zmq::socket_t &zap_auth);
|
||||
|
||||
/// Handles a control message from some outer thread to the proxy
|
||||
void proxy_control_message(std::list<zmq::message_t> parts);
|
||||
|
||||
/// Closing any idle connections that have outlived their idle time. Note that this only
|
||||
/// affects outgoing connections; incomings connections are the responsibility of the other end.
|
||||
void proxy_expire_idle_peers();
|
||||
|
||||
/// Closes an outgoing connection immediately, updates internal variables appropriately.
|
||||
/// Returns the next iterator (the original may or may not be removed from peers, depending on
|
||||
/// whether or not it also has an active incoming connection).
|
||||
decltype(peers)::iterator proxy_close_outgoing(decltype(peers)::iterator it);
|
||||
|
||||
|
||||
/// End of proxy-specific members
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
/// Callbacks for data commands. Must be fully populated before starting SNNetwork instances
|
||||
/// as this is accessed without a lock from worker threads.
|
||||
///
|
||||
/// The value is the {callback, public} pair where `public` is true if unauthenticated
|
||||
/// connections may call this and false if only authenricated SNs may invoke the command.
|
||||
static std::unordered_map<std::string, std::pair<void(*)(SNNetwork::message &message, void *data), command_type>> commands;
|
||||
static bool commands_mutable;
|
||||
|
||||
/// Starts up the proxy thread; called during construction
|
||||
void launch_proxy_thread(const std::vector<std::string> &bind);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs a SNNetwork connection listening on the given bind string.
|
||||
*
|
||||
* @param pubkey the service node's public key (32-byte binary string)
|
||||
* @param privkey the service node's private key (32-byte binary string)
|
||||
* @param bind list of addresses to bind to. Can be any string zmq supports; typically a tcp
|
||||
* IP/port combination such as: "tcp://\*:4567" or "tcp://1.2.3.4:5678".
|
||||
* @param peer_lookup function that takes a pubkey key (32-byte binary string) and returns a
|
||||
* connection string such as "tcp://1.2.3.4:23456" to which a connection should be established
|
||||
* to reach that service node. Note that this function is only called if there is no existing
|
||||
* connection to that service node, and that the function is never called for a connection to
|
||||
* self (that uses an internal connection instead).
|
||||
* @param allow_incoming called on incoming connections with the (verified) incoming connection
|
||||
* pubkey (32-byte binary string) to determine whether the given SN should be allowed to
|
||||
* connect.
|
||||
* @param want_log
|
||||
* @param log a function pointer (or non-capturing lambda) to call to get a std::ostream pointer
|
||||
* to send output to, or nullptr to suppress output. Optional; if omitted the default returns
|
||||
* std::cerr for WARN and higher.
|
||||
* @param max_workers the maximum number of simultaneous worker threads to start. Defaults to
|
||||
* std::thread::hardware_concurrency(). Note that threads are only started on demand (i.e. when
|
||||
* a request arrives when all existing threads are busy handling requests).
|
||||
*/
|
||||
SNNetwork(std::string pubkey,
|
||||
std::string privkey,
|
||||
const std::vector<std::string> &bind,
|
||||
LookupFunc peer_lookup,
|
||||
AllowFunc allow_connection,
|
||||
WantLog want_log = [](LogLevel l) { return l >= LogLevel::warn; },
|
||||
WriteLog logger = [](LogLevel, const char *f, int line, std::string msg) { std::cerr << f << ':' << line << ": " << msg << std::endl; },
|
||||
unsigned int max_workers = std::thread::hardware_concurrency());
|
||||
|
||||
/** Constructs a SNNetwork that does not listen but can be used for connecting to remote
|
||||
* listening service nodes, for example to submit blink transactions to service nodes. It
|
||||
* generates a non-persistant x25519 key pair on startup (for encrypted communication with
|
||||
* peers).
|
||||
*/
|
||||
SNNetwork(LookupFunc peer_lookup,
|
||||
AllowFunc allow_connection,
|
||||
WantLog want_log = [](LogLevel l) { return l >= LogLevel::warn; },
|
||||
WriteLog logger = [](LogLevel, const char *f, int line, std::string msg) { std::cerr << f << ':' << line << ": " << msg << std::endl; },
|
||||
unsigned int max_workers = std::thread::hardware_concurrency());
|
||||
|
||||
/**
|
||||
* Destructor; instructs the proxy to quit. The proxy tells all workers to quit, waits for them
|
||||
* to quit and rejoins the threads then quits itself. The outer thread (where the destructor is
|
||||
* running) rejoins the proxy thread.
|
||||
*/
|
||||
~SNNetwork();
|
||||
|
||||
/**
|
||||
* Returns true if we are running as a service node, which (currently) is synonymous with us
|
||||
* being started in listening mode.
|
||||
*/
|
||||
bool is_service_node() const { return (bool) listener; }
|
||||
|
||||
/**
|
||||
* Try to initiate a connection to the given SN in anticipation of needing a connection in the
|
||||
* future. If a connection is already established, the connection's idle timer will be reset
|
||||
* (so that the connection will not be closed too soon). If the given idle timeout is greater
|
||||
* than the current idle timeout then the timeout increases to the new value; if less than the
|
||||
* current timeout it is ignored. (Note that idle timeouts only apply if the existing
|
||||
* connection is an outgoing connection).
|
||||
*
|
||||
* Note that this method (along with send) doesn't block waiting for a connection; it merely
|
||||
* instructs the proxy thread that it should establish a connection.
|
||||
*
|
||||
* @param pubkey - the public key (32-byte binary string) of the service node to connect to
|
||||
* @param keep_alive - the connection will be kept alive if there was valid activity within
|
||||
* the past `keep_alive` milliseconds. If an outgoing connection already
|
||||
* exists, the longer of the existing and the given keep alive is used.
|
||||
* Note that the default applied here is much longer than the default for an
|
||||
* implicit connect() by calling send() directly.
|
||||
* @param hint - if non-empty and a new outgoing connection needs to be made this hint value
|
||||
* may be used instead of calling the lookup function. (Note that there is no
|
||||
* guarantee that the hint will be used; it should only be used when the lookup
|
||||
* value has been precomputed to save a lookup call).
|
||||
*/
|
||||
void connect(const std::string &pubkey, std::chrono::milliseconds keep_alive = 5min, const std::string &hint = "");
|
||||
|
||||
/**
|
||||
* Queue a message to be relayed to SN identified with the given pubkey without expecting a
|
||||
* reply. The SN will attempt to relay (first connecting and handshaking if not already
|
||||
* connected to the given SN).
|
||||
*
|
||||
* If a new connection it established it will have a relatively short (15s) idle timeout. If
|
||||
* the connection should stay open longer you should call `connect(pubkey, IDLETIME)` first.
|
||||
*
|
||||
* Note that this method (along with connect) doesn't block waiting for a connection or for the
|
||||
* message to send; it merely instructs the proxy thread that it should send.
|
||||
*
|
||||
* @param pubkey - the pubkey to send this to
|
||||
* @param value - a bt_serializable value to serialize and send
|
||||
* @param opts - any number of bt_serializable values and send options. Each send option affect
|
||||
* how the send works; each serializable value becomes a serialized message part.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* sn.send(pubkey, "hello", "abc", 42, send_option::hint("tcp://localhost:1234"));
|
||||
*
|
||||
* sends the command `hello` to the given pubkey, containing two additional message parts of
|
||||
* serialized "abc" and 42 values, and, if not currently connected, using the given connection
|
||||
* hint rather than performing a connection address lookup on the pubkey.
|
||||
*/
|
||||
template <typename... T>
|
||||
void send(const std::string &pubkey, const std::string &cmd, const T &...opts);
|
||||
|
||||
/** The keep-alive time for a send() that results in a new connection. To use a longer
|
||||
* keep-alive to a host call `connect()` first with the desired keep-alive time or pass the
|
||||
* send_option::keep_alive
|
||||
*/
|
||||
static constexpr std::chrono::milliseconds default_send_keep_alive{15000};
|
||||
|
||||
/// The key pair this SN was created with
|
||||
const std::string &get_pubkey() const { return pubkey; }
|
||||
const std::string &get_privkey() const { return privkey; }
|
||||
|
||||
/**
|
||||
* Registers a command that may be invoked on a quorumnet connection. The quorum command
|
||||
* is one of three types:
|
||||
* - `SNNetwork::command_type::quorum` - for a command that is only permitted between
|
||||
* registered service nodes. It will be discarded if received from a remote that is not
|
||||
* recognized as a service node, or if the local node is not a service node.
|
||||
* - `SNNetwork::command_type::public_` - for a command that is permitted from anyone, but
|
||||
* only if the local node is running as a service node.
|
||||
* - `SNNetwork::command_type::response` - for a command that can be issued by a service
|
||||
* node to the local client (whether or not running as a service node), typically issued
|
||||
* in response to a `public_` command issued by this service node.
|
||||
*
|
||||
* Commands may only be registered before any SNNetwork instance has been constructed.
|
||||
*
|
||||
* @param command - the command string to assign. If it already exists it will be replaced.
|
||||
* @param callback - a callback that takes the message info and the opaque `data` pointer
|
||||
* @param cmd_type - the command type, as described above.
|
||||
*/
|
||||
static void register_command(
|
||||
std::string command,
|
||||
command_type cmd_type,
|
||||
void(*callback)(SNNetwork::message &message, void *data));
|
||||
};
|
||||
|
||||
/// Namespace for options to the send() method
|
||||
namespace send_option {
|
||||
|
||||
/// `serialized` lets you serialize once when sending the same data to many peers by constructing a
|
||||
/// single serialized option and passing it repeatedly rather than needing to reserialize on each
|
||||
/// send.
|
||||
struct serialized {
|
||||
std::string data;
|
||||
template <typename T>
|
||||
serialized(const T &arg) : data{quorumnet::bt_serialize(arg)} {}
|
||||
};
|
||||
|
||||
/// Specifies a connection hint when passed in to send(). If there is no current connection to the
|
||||
/// peer then the hint is used to save a call to the LookupFunc to get the connection location.
|
||||
/// (Note that there is no guarantee that the given hint will be used or that a LookupFunc call will
|
||||
/// not be done.)
|
||||
struct hint {
|
||||
std::string connect_hint;
|
||||
hint(std::string connect_hint) : connect_hint{std::move(connect_hint)} {}
|
||||
};
|
||||
|
||||
/// Does a send() if we already have a connection (incoming or outgoing) with the given peer,
|
||||
/// otherwise drops the message.
|
||||
struct optional {};
|
||||
|
||||
/// Specifies that the message should be sent only if it can be sent on an existing incoming socket,
|
||||
/// and dropped otherwise.
|
||||
struct incoming {};
|
||||
|
||||
/// Specifies the idle timeout for the connection - if a new or existing outgoing connection is used
|
||||
/// for the send and its current idle timeout setting is less than this value then it is updated.
|
||||
struct keep_alive {
|
||||
std::chrono::milliseconds time;
|
||||
keep_alive(std::chrono::milliseconds time) : time{std::move(time)} {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Sends a control message to the given socket consisting of the command plus optional dict
|
||||
// data (only sent if the dict is non-empty).
|
||||
void send_control(zmq::socket_t &sock, const std::string &cmd, const bt_dict &data = {});
|
||||
|
||||
/// Base case: takes a serializable value and appends it to the message parts
|
||||
template <typename T>
|
||||
void apply_send_option(bt_list &parts, bt_dict &, const T &arg) {
|
||||
parts.push_back(quorumnet::bt_serialize(arg));
|
||||
}
|
||||
|
||||
/// `serialized` specialization: lets you serialize once when sending the same data to many peers
|
||||
template <> inline void apply_send_option(bt_list &parts, bt_dict &, const send_option::serialized &serialized) {
|
||||
parts.push_back(serialized.data);
|
||||
}
|
||||
|
||||
/// `hint` specialization: sets the hint in the control data
|
||||
template <> inline void apply_send_option(bt_list &, bt_dict &control_data, const send_option::hint &hint) {
|
||||
control_data["hint"] = hint.connect_hint;
|
||||
}
|
||||
|
||||
/// `optional` specialization: sets the optional flag in the control data
|
||||
template <> inline void apply_send_option(bt_list &, bt_dict &control_data, const send_option::optional &) {
|
||||
control_data["optional"] = 1;
|
||||
}
|
||||
|
||||
/// `incoming` specialization: sets the optional flag in the control data
|
||||
template <> inline void apply_send_option(bt_list &, bt_dict &control_data, const send_option::incoming &) {
|
||||
control_data["incoming"] = 1;
|
||||
}
|
||||
|
||||
/// `keep_alive` specialization: increases the outgoing socket idle timeout (if shorter)
|
||||
template <> inline void apply_send_option(bt_list &, bt_dict &control_data, const send_option::keep_alive &timeout) {
|
||||
control_data["keep-alive"] = timeout.time.count();
|
||||
}
|
||||
|
||||
/// Calls apply_send_option on each argument and returns a bt_dict with the command plus data stored
|
||||
/// in the "send" key plus whatever else is implied by any given option arguments.
|
||||
template <typename... T>
|
||||
bt_dict send_control_data(const std::string &cmd, const T &...opts) {
|
||||
bt_dict control_data;
|
||||
bt_list parts{{cmd}};
|
||||
#ifdef __cpp_fold_expressions
|
||||
(detail::apply_send_option(parts, control_data, opts),...);
|
||||
#else
|
||||
(void) std::initializer_list<int>{(detail::apply_send_option(parts, control_data, opts), 0)...};
|
||||
#endif
|
||||
|
||||
control_data["send"] = std::move(parts);
|
||||
return control_data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
void SNNetwork::send(const std::string &pubkey, const std::string &cmd, const T &...opts) {
|
||||
bt_dict control_data = detail::send_control_data(cmd, opts...);
|
||||
control_data["pubkey"] = pubkey;
|
||||
detail::send_control(get_control_socket(), "SEND", control_data);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void SNNetwork::message::reply(const std::string &command, Args &&...args) {
|
||||
if (sn) net.send(pubkey, command, std::forward<Args>(args)...);
|
||||
else net.send(pubkey, command, send_option::optional{}, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Creates a hex string from a character sequence.
|
||||
template <typename It>
|
||||
std::string as_hex(It begin, It end) {
|
||||
constexpr std::array<char, 16> lut{{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}};
|
||||
std::string hex;
|
||||
using std::distance;
|
||||
hex.reserve(distance(begin, end) * 2);
|
||||
while (begin != end) {
|
||||
char c = *begin;
|
||||
hex += lut[(c & 0xf0) >> 4];
|
||||
hex += lut[c & 0x0f];
|
||||
++begin;
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
template <typename String>
|
||||
inline std::string as_hex(const String &s) {
|
||||
using std::begin;
|
||||
using std::end;
|
||||
return as_hex(begin(s), end(s));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// vim:sw=4:et
|
|
@ -50,6 +50,8 @@
|
|||
#include <boost/format.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <lokimq/hex.h>
|
||||
#include <lokimq/string_view.h>
|
||||
#include "include_base_utils.h"
|
||||
#include "common/i18n.h"
|
||||
#include "common/command_line.h"
|
||||
|
@ -57,7 +59,6 @@
|
|||
#include "common/dns_utils.h"
|
||||
#include "common/base58.h"
|
||||
#include "common/scoped_message_writer.h"
|
||||
#include "common/hex.h"
|
||||
#include "common/loki_integration_test_hooks.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
|
||||
#include "cryptonote_core/service_node_voting.h"
|
||||
|
@ -650,14 +651,9 @@ namespace
|
|||
|
||||
void print_secret_key(const crypto::secret_key &k)
|
||||
{
|
||||
static constexpr const char hex[] = u8"0123456789abcdef";
|
||||
const uint8_t *ptr = (const uint8_t*)k.data;
|
||||
for (size_t i = 0, sz = sizeof(k); i < sz; ++i)
|
||||
{
|
||||
putchar(hex[*ptr >> 4]);
|
||||
putchar(hex[*ptr & 15]);
|
||||
++ptr;
|
||||
}
|
||||
lokimq::string_view data{reinterpret_cast<const char*>(k.data), sizeof(k.data)};
|
||||
std::ostream_iterator<char> osi{std::cout};
|
||||
lokimq::to_hex(data.begin(), data.end(), osi);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -713,9 +709,9 @@ bool simple_wallet::viewkey(const std::vector<std::string> &args/* = std::vector
|
|||
std::cout << "secret: On device. Not available" << std::endl;
|
||||
} else {
|
||||
SCOPED_WALLET_UNLOCK();
|
||||
printf("secret: ");
|
||||
std::cout << "secret: ";
|
||||
print_secret_key(m_wallet->get_account().get_keys().m_view_secret_key);
|
||||
putchar('\n');
|
||||
std::cout << '\n';
|
||||
}
|
||||
std::cout << "public: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_view_public_key) << std::endl;
|
||||
|
||||
|
@ -735,9 +731,9 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto
|
|||
std::cout << "secret: On device. Not available" << std::endl;
|
||||
} else {
|
||||
SCOPED_WALLET_UNLOCK();
|
||||
printf("secret: ");
|
||||
std::cout << "secret: ";
|
||||
print_secret_key(m_wallet->get_account().get_keys().m_spend_secret_key);
|
||||
putchar('\n');
|
||||
std::cout << '\n';
|
||||
}
|
||||
std::cout << "public: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key) << std::endl;
|
||||
|
||||
|
@ -4208,7 +4204,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
|
|||
PAUSE_READLINE();
|
||||
std::cout << tr("View key: ");
|
||||
print_secret_key(m_wallet->get_account().get_keys().m_view_secret_key);
|
||||
putchar('\n');
|
||||
std::cout << '\n';
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
@ -6644,14 +6640,10 @@ bool simple_wallet::print_lns_owners_to_names(const std::vector<std::string>& ar
|
|||
fail_msg_writer() << "arg is not a 64 character ed25519 public key, arg = " << arg;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (char c : arg)
|
||||
if (!lokimq::is_hex(arg))
|
||||
{
|
||||
if (!hex::char_is_hex(c))
|
||||
{
|
||||
fail_msg_writer() << "arg contains a non-hex character = " << c << ", arg = " << arg;
|
||||
return false;
|
||||
}
|
||||
fail_msg_writer() << "arg contains non-hex characters: " << arg;
|
||||
return false;
|
||||
}
|
||||
request.entries.push_back(arg);
|
||||
}
|
||||
|
|
|
@ -68,11 +68,11 @@ for s in states:
|
|||
socket.connect("tcp://{}:{}".format(ip, port))
|
||||
print("Ping {}:{} (for SN {})".format(ip, port, pk))
|
||||
bt_tag = bytes("i{}e".format(tag), "utf-8")
|
||||
socket.send_multipart((b"ping", b"d1:!" + bt_tag + b"e"))
|
||||
socket.send_multipart((b"ping.ping", b"d1:!" + bt_tag + b"e"))
|
||||
ponged = False
|
||||
while socket.poll(timeout=5000):
|
||||
m = socket.recv_multipart()
|
||||
if len(m) == 2 and m[0] == b'pong':
|
||||
if len(m) == 2 and m[0] == b'ping.pong':
|
||||
ponged = True
|
||||
if m[1] == b'd1:!' + bt_tag + b'2:sni1ee':
|
||||
print("Received pong, we were recognized as a SN")
|
||||
|
|
Loading…
Reference in New Issue