Add wire::json_writer, and use in some of ZMQ-PUB functions.

This commit is contained in:
Lee *!* Clagett
2025-11-28 21:20:52 -05:00
committed by Lee Clagett
parent fb5b06bd3b
commit 108244cdf5
22 changed files with 1030 additions and 101 deletions
+1 -8
View File
@@ -27,24 +27,17 @@
#pragma once
#include "serialization/wire/error.h"
#include "serialization/wire/fwd.h"
#include "serialization/wire/read.h"
#include "serialization/wire/write.h"
//! Define functions that list fields in `type` (using virtual interface)
#define WIRE_DEFINE_OBJECT(type, map) \
void read_bytes(::wire::reader& source, type& dest) \
{ map(source, dest); } \
\
void write_bytes(::wire::writer& dest, const type& source) \
{ map(dest, source); }
//! Define `from_bytes` and `to_bytes` for `this`.
#define WIRE_DEFINE_CONVERSIONS() \
template<typename R, typename T> \
std::error_code from_bytes(T&& source) \
{ return ::wire_read::from_bytes<R>(std::forward<T>(source), *this); } \
\
template<typename W, typename T> \
std::error_code to_bytes(T& dest) const \
{ return ::wire_write::to_bytes<W>(dest, *this); }
@@ -0,0 +1,56 @@
// Copyright (c) 2021, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <string>
#include "serialization/wire/json/base.h"
#include "serialization/wire/json/fwd.h"
#include "serialization/wire/json/write.h"
#include "serialization/wire/write.h"
#include "span.h"
//! Define functions that list fields in `type` (calls de-virtualized)
#define WIRE_JSON_DEFINE_OBJECT(type, map) \
void read_bytes(::wire::json_reader& source, type& dest) \
{ map(source, dest); } \
\
void write_bytes(::wire::json_writer& dest, const type& source) \
{ map(dest, source); }
//! Define functions that convert `type` to/from json bytes
#define WIRE_JSON_DEFINE_CONVERSION(type) \
std::error_code convert_from_json(const ::epee::span<const char> source, type& dest) \
{ return ::wire_read::from_bytes<::wire::json_reader>(source, dest); } \
\
std::error_code convert_to_json(std::string& dest, const type& source) \
{ return ::wire_write::to_bytes<::wire::json_string_writer>(dest, source); }
//! Define functions that convert `type::request` and `type::response` to/from json bytes
#define WIRE_JSON_DEFINE_COMMAND(type) \
WIRE_JSON_DEFINE_CONVERSION(type::request) \
WIRE_JSON_DEFINE_CONVERSION(type::response)
@@ -0,0 +1,93 @@
// Copyright (c) 2022, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <string>
#include <system_error>
#include "byte_stream.h"
#include "serialization/wire/json/fwd.h"
#include "span.h"
//! Declare functions that convert `type` to/from json bytes
#define WIRE_JSON_DECLARE_CONVERSION(type) \
std::error_code convert_from_json(::epee::span<const char>, type&); \
std::error_code convert_to_json(std::string&, const type&) \
//! Declare functions that convert `type::request` and `type::response` to/from json bytes
#define WIRE_JSON_DECLARE_COMMAND(type) \
WIRE_JSON_DECLARE_CONVERSION(type::request); \
WIRE_JSON_DECLARE_CONVERSION(type::response)
namespace wire
{
struct json
{
using input_type = json_reader;
using output_type = json_string_writer;
//! Only enabled if `T` has templated method `from_bytes`.
template<typename T>
static auto from_bytes(const epee::span<const char> source, T& dest) -> decltype(dest.template from_bytes<input_type>(source))
{
return dest.template from_bytes<input_type>(source);
}
//! Only enabled if `T` has templated method `to_bytes`.
template<typename T>
static auto to_bytes(std::string& dest, const T& source) -> decltype(source.template to_bytes<output_type>(dest))
{
return source.template to_bytes<output_type>(dest);
}
// Parameters packs have lower precedence; above functions are preferred.
template<typename... T>
static std::error_code from_bytes(const epee::span<const char> source, T&... dest)
{
return convert_from_json(source, dest...); // ADL (searches every associated namespace)
}
template<typename... T>
static std::error_code to_bytes(std::string& dest, const T&... source)
{
return convert_to_json(dest, source...); // ADL (searches every associated namespace)
}
template<typename T>
static std::error_code to_bytes(epee::byte_stream& dest, const T& source)
{
std::string buf{};
const std::error_code error = to_bytes(buf, source);
if (!error)
dest.write(epee::to_span(buf));
return error;
}
};
}
@@ -0,0 +1,42 @@
// Copyright (c) 2022, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
//! Declare function that list fields `type` (calls de-virtualized)
#define WIRE_JSON_DECLARE_OBJECT(type) \
void read_bytes(::wire::json_reader&, type&); \
void write_bytes(::wire::json_writer&, const type&)
namespace wire
{
struct json;
class json_reader;
struct json_string_writer;
class json_writer;
}
@@ -0,0 +1,168 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <array>
#include <cstdint>
#include <limits>
#include <rapidjson/writer.h>
#include <string>
#include <string_view>
#include "serialization/wire/write.h"
#include "span.h"
namespace wire
{
constexpr const std::size_t uint_to_string_size =
std::numeric_limits<std::uintmax_t>::digits10 + 2;
struct string_stream
{
using Ch = char;
std::string buffer_;
void Put(const char c) { buffer_.push_back(c); }
static void Flush() noexcept {}
};
//! Compatability/optimization for rapidjson.
inline void PutReserve(string_stream& dest, const std::size_t length)
{
dest.buffer_.reserve(length);
}
//! Compability/optimization for rapidjson.
inline void PutN(string_stream& dest, const std::uint8_t ch, const std::size_t count)
{
dest.buffer_.append(count, ch);
}
//! Writes JSON tokens one-at-a-time for DOMless output.
class json_writer : public writer
{
string_stream stream_;
rapidjson::Writer<string_stream> formatter_;
bool needs_flush_;
virtual void do_flush(epee::span<const char>);
//! Flush written bytes to `do_flush(...)` if configured
void check_flush();
protected:
json_writer(bool needs_flush)
: writer(), stream_{}, formatter_(stream_), needs_flush_(needs_flush)
{}
json_writer(std::string&& bytes)
: writer(), stream_{std::move(bytes)}, formatter_(stream_), needs_flush_(false)
{}
//! \throw std::logic_error if incomplete JSON tree. \return JSON bytes
std::string take_json();
//! Flush bytes in local buffer to `do_flush(...)`
void flush()
{
do_flush(epee::to_span(stream_.buffer_));
stream_.buffer_.clear();
}
public:
json_writer(const json_writer&) = delete;
virtual ~json_writer() noexcept;
json_writer& operator=(const json_writer&) = delete;
//! JSON does not have length field for arrays
static constexpr std::false_type need_array_size() noexcept { return {}; }
//! \return Null-terminated buffer containing uint as decimal ascii
static std::array<char, uint_to_string_size> to_string(std::uintmax_t) noexcept;
//! \throw std::logic_error if incomplete JSON tree
void check_complete();
void boolean(bool) override final;
void integer(std::intmax_t) override final;
void unsigned_integer(std::uintmax_t) override final;
void real(double) override final;
void string(std::string_view) override final;
void binary(epee::span<const std::uint8_t>) override final;
void start_array(std::size_t) override final;
void end_array() override final;
void start_object(std::size_t) override final;
void key(std::string_view) override final;
void binary_key(epee::span<const std::uint8_t>) override final;
void end_object() override final;
};
//! Buffers entire JSON message in memory
struct json_string_writer final : json_writer
{
//! Write json to an empty buffer.
explicit json_string_writer()
: json_string_writer(std::string{})
{}
//! Append json to existing `buffer`.
explicit json_string_writer(std::string&& buffer)
: json_writer(std::move(buffer))
{}
//! \throw std::logic_error if incomplete JSON tree \return JSON bytes
std::string take_buffer()
{
return json_writer::take_json();
}
};
//! Periodically flushes JSON data to `std::ostream`
class json_stream_writer final : public json_writer
{
std::ostream& dest;
virtual void do_flush(epee::span<const char>) override final;
public:
explicit json_stream_writer(std::ostream& dest)
: json_writer(true), dest(dest)
{}
//! Flush remaining bytes to stream \throw std::logic_error if incomplete JSON tree
void finish()
{
check_complete();
flush();
}
};
}
@@ -0,0 +1,73 @@
// Copyright (c) 2025, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <type_traits>
#include <utility>
#include "serialization/wire/field.h"
#include "serialization/wire/traits.h"
#include "serialization/wire.h"
namespace wire
{
//! A wrapper that tells `wire::writer`s to write the inner type as an array.
template<typename U>
struct range_
{
WIRE_DEFINE_CONVERSIONS()
using value_type = unwrap_reference_t<U>;
U value;
constexpr const value_type& get_value() const noexcept { return value; }
value_type& get_value() noexcept { return value; }
// concept requirements for `is_optional_on_empty`
bool empty() const { return get_value().empty(); }
};
template<typename F, typename T>
void write_bytes(F& format, const range_<T> self)
{
wire_write::array(format, self.get_value());
}
//! Links `value` with `range_`.
template<typename T>
inline constexpr range_<T> range(T value)
{
return {std::move(value)};
}
template<typename T>
struct is_optional_on_empty<range_<T>>
: std::true_type
{};
} // wire
@@ -27,10 +27,10 @@
#pragma once
#include <boost/utility/string_ref.hpp>
#include <boost/range/size.hpp>
#include <cstdint>
#include <iterator>
#include <string_view>
#include <system_error>
#include <type_traits>
@@ -73,14 +73,14 @@ namespace wire
virtual void real(double) = 0;
virtual void string(boost::string_ref) = 0;
virtual void string(std::string_view) = 0;
virtual void binary(epee::span<const std::uint8_t>) = 0;
virtual void start_array(std::size_t) = 0;
virtual void end_array() = 0;
virtual void start_object(std::size_t) = 0;
virtual void key(boost::string_ref) = 0;
virtual void key(std::string_view) = 0;
virtual void binary_key(epee::span<const std::uint8_t>) = 0;
virtual void end_object() = 0;
@@ -129,7 +129,7 @@ namespace wire
{ write_arithmetic(dest, source); }
template<typename W>
inline void write_bytes(W& dest, const boost::string_ref source)
inline void write_bytes(W& dest, const std::string_view source)
{ dest.string(source); }
template<typename W, typename T>
@@ -245,7 +245,7 @@ namespace wire_write
}
template<typename W>
inline void dynamic_object_key(W& dest, const boost::string_ref source)
inline void dynamic_object_key(W& dest, const std::string_view source)
{
dest.key(source);
}
+1
View File
@@ -87,3 +87,4 @@ target_include_directories(epee
"${EPEE_INCLUDE_DIR_BASE}"
"${OPENSSL_INCLUDE_DIR}")
add_subdirectory(wire)
+34
View File
@@ -0,0 +1,34 @@
# Copyright (c) 2025, The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(wire_sources error.cpp write.cpp)
add_library(wire ${wire_sources})
target_link_libraries(wire epee)
add_subdirectory(json)
+97
View File
@@ -0,0 +1,97 @@
// Copyright (c) 2021, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "serialization/wire/error.h"
namespace wire
{
namespace error
{
const char* get_string(const schema value) noexcept
{
switch (value)
{
default:
break;
case schema::none:
return "No schema errors";
case schema::array:
return "Schema expected array";
case schema::array_max_element:
return "Schema expected array size to be smaller";
case schema::array_min_size:
return "Schema expected minimum wire size per array element to be larger";
case schema::binary:
return "Schema expected binary value of variable size";
case schema::boolean:
return "Schema expected boolean value";
case schema::enumeration:
return "Schema expected a specific of enumeration value(s)";
case schema::fixed_binary:
return "Schema expected binary of fixed size";
case schema::integer:
return "Schema expected integer value";
case schema::invalid_key:
return "Schema does not allow object field key";
case schema::larger_integer:
return "Schema expected a larger integer value";
case schema::maximum_depth:
return "Schema hit maximum array+object depth tracking";
case schema::missing_key:
return "Schema missing required field key";
case schema::number:
return "Schema expected number (integer or float) value";
case schema::object:
return "Schema expected object";
case schema::smaller_integer:
return "Schema expected a smaller integer value";
case schema::string:
return "Schema expected string";
}
return "Unknown schema error";
}
const std::error_category& schema_category() noexcept
{
struct category final : std::error_category
{
virtual const char* name() const noexcept override final
{
return "wire::error::schema_category()";
}
virtual std::string message(int value) const override final
{
return get_string(schema(value));
}
};
static const category instance{};
return instance;
}
}
}
+32
View File
@@ -0,0 +1,32 @@
# Copyright (c) 2025, The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(wire-json_sources write.cpp)
add_library(wire-json ${wire-json_sources})
target_link_libraries(wire-json wire)
+154
View File
@@ -0,0 +1,154 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "serialization/wire/json/write.h"
#include <boost/container/small_vector.hpp>
#include <stdexcept>
#include "hex.h"
namespace
{
constexpr const unsigned flush_threshold = 100;
constexpr const unsigned max_buffer = 4096;
boost::container::small_vector<char, 256> json_hex(const epee::span<const std::uint8_t> source)
{
boost::container::small_vector<char, 256> buffer;
buffer.resize(source.size() * 2);
if (!epee::to_hex::buffer(epee::to_mut_span(buffer), source))
throw std::logic_error{"Invalid buffer size for binary->hex conversion in json_writer"};
return buffer;
}
}
namespace wire
{
void json_writer::do_flush(epee::span<const char>)
{}
void json_writer::check_flush()
{
if (needs_flush_ && (max_buffer < stream_.buffer_.size() || stream_.buffer_.capacity() - stream_.buffer_.size() < flush_threshold))
flush();
}
void json_writer::check_complete()
{
if (!formatter_.IsComplete())
throw std::logic_error{"json_writer::take_json() failed with incomplete JSON tree"};
}
std::string json_writer::take_json()
{
check_complete();
std::string out{std::move(stream_.buffer_)};
stream_.buffer_.clear();
formatter_.Reset(stream_);
return out;
}
json_writer::~json_writer() noexcept
{}
std::array<char, uint_to_string_size> json_writer::to_string(const std::uintmax_t value) noexcept
{
static_assert(std::numeric_limits<std::uintmax_t>::max() <= std::numeric_limits<std::uint64_t>::max(), "bad uint conversion");
std::array<char, uint_to_string_size> buf{{}};
rapidjson::internal::u64toa(std::uint64_t(value), buf.data());
return buf;
}
void json_writer::boolean(const bool source)
{
formatter_.Bool(source);
check_flush();
}
void json_writer::integer(const std::intmax_t source)
{
static_assert(std::numeric_limits<std::int64_t>::min() <= std::numeric_limits<std::intmax_t>::min(), "too small");
static_assert(std::numeric_limits<std::intmax_t>::max() <= std::numeric_limits<std::int64_t>::max(), "too large");
formatter_.Int64(source);
check_flush();
}
void json_writer::unsigned_integer(const std::uintmax_t source)
{
static_assert(std::numeric_limits<std::uintmax_t>::max() <= std::numeric_limits<std::uint64_t>::max(), "too large");
formatter_.Uint64(source);
check_flush();
}
void json_writer::real(const double source)
{
formatter_.Double(source);
check_flush();
}
void json_writer::string(const std::string_view source)
{
formatter_.String(source.data(), source.size());
check_flush();
}
void json_writer::binary(const epee::span<const std::uint8_t> source)
{
const auto buffer = json_hex(source);
string({buffer.data(), buffer.size()});
}
void json_writer::start_array(std::size_t)
{
formatter_.StartArray();
}
void json_writer::end_array()
{
formatter_.EndArray();
}
void json_writer::start_object(std::size_t)
{
formatter_.StartObject();
}
void json_writer::key(const std::string_view str)
{
formatter_.Key(str.data(), str.size());
check_flush();
}
void json_writer::binary_key(const epee::span<const std::uint8_t> source)
{
const auto buffer = json_hex(source);
key({buffer.data(), buffer.size()});
}
void json_writer::end_object()
{
formatter_.EndObject();
}
void json_stream_writer::do_flush(const epee::span<const char> bytes)
{
dest.write(bytes.data(), bytes.size());
}
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) 2025, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "serialization/wire/write.h"
wire::writer::~writer() noexcept
{}
+7
View File
@@ -38,6 +38,7 @@
#include "generic-ops.h"
#include "hex.h"
#include "span.h"
#include "serialization/wire/traits.h"
namespace crypto {
@@ -108,3 +109,9 @@ namespace crypto {
CRYPTO_MAKE_HASHABLE(hash)
CRYPTO_MAKE_COMPARABLE(hash8)
namespace wire
{
WIRE_DECLARE_BLOB_NS(crypto::hash);
WIRE_DECLARE_BLOB_NS(crypto::hash8);
}
@@ -45,6 +45,7 @@ using namespace epee;
#include "crypto/crypto.h"
#include "crypto/hash.h"
#include "ringct/rctSigs.h"
#include "serialization/wire.h"
using namespace crypto;
@@ -217,6 +218,16 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
namespace
{
template<typename F, typename T>
void map_backlog_entry(F& format, T& self)
{
wire::object(format, WIRE_FIELD(id), WIRE_FIELD(weight), WIRE_FIELD(fee));
}
}
WIRE_DEFINE_OBJECT(tx_block_template_backlog_entry, map_backlog_entry);
//---------------------------------------------------------------
crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr)
{
account_public_address addr = {null_pkey, null_pkey};
@@ -32,6 +32,7 @@
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "ringct/rctOps.h"
#include "serialization/wire/fwd.h"
namespace cryptonote
{
@@ -114,6 +115,7 @@ namespace cryptonote
uint64_t weight;
uint64_t fee;
};
WIRE_DECLARE_OBJECT(tx_block_template_backlog_entry);
//---------------------------------------------------------------
crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr);
+3
View File
@@ -147,7 +147,9 @@ target_link_libraries(rpc_pub
epee
net
cryptonote_basic
cryptonote_core
serialization
wire-json
${Boost_THREAD_LIBRARY})
target_link_libraries(daemon_messages
@@ -167,6 +169,7 @@ target_link_libraries(daemon_rpc_server
version
daemon_messages
serialization
wire-json
${Boost_REGEX_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
+128 -62
View File
@@ -51,6 +51,10 @@
#include "serialization/json_object.h"
#include "ringct/rctTypes.h"
#include "cryptonote_core/cryptonote_tx_utils.h"
#include "serialization/wire.h"
#include "serialization/wire/adapted/vector.h"
#include "serialization/wire/json.h"
#include "serialization/wire/wrapper/range.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "net.zmq"
@@ -59,9 +63,58 @@ namespace
{
constexpr const char txpool_signal[] = "tx_signal";
using chain_writer = void(epee::byte_stream&, std::uint64_t, epee::span<const cryptonote::block>);
using miner_writer = void(epee::byte_stream&, uint8_t, uint64_t, const crypto::hash&, const crypto::hash&, cryptonote::difficulty_type, uint64_t, uint64_t, const std::vector<cryptonote::tx_block_template_backlog_entry>&);
using txpool_writer = void(epee::byte_stream&, epee::span<const cryptonote::txpool_event>);
using chain_writer = std::error_code(epee::byte_stream&, std::uint64_t, epee::span<const cryptonote::block>);
using miner_writer = std::error_code(epee::byte_stream&, uint8_t, uint64_t, const crypto::hash&, const crypto::hash&, cryptonote::difficulty_type, uint64_t, uint64_t, const std::vector<cryptonote::tx_block_template_backlog_entry>&);
using txpool_writer = std::error_code(epee::byte_stream&, epee::span<const cryptonote::txpool_event>);
namespace error
{
enum class code : int
{
none = 0, //!< Must be zero for `expect<..>`
bad_block_hash //!< Unable to compute block hash
};
//! \return Error message string.
const char* get_string(code value) noexcept
{
switch (value)
{
case code::none:
return "no error";
case code::bad_block_hash:
return "Unable to hash block";
default:
break;
};
return "invalid zmq/pub error code";
}
//! \return Category for `code`.
const std::error_category& category() noexcept
{
struct category final : std::error_category
{
virtual const char* name() const noexcept override final
{
return "zmq_pub::error_category()";
}
virtual std::string message(int value) const override final
{
return get_string(code(value));
}
};
static const category instance{};
return instance;
}
//! \return Error code with `value` and `schema_category()`.
inline std::error_code make_error_code(const code value) noexcept
{
return std::error_code{int(value), category()};
}
}
template<typename F>
struct context
@@ -106,23 +159,48 @@ namespace
//! \return `name:...` where `...` is JSON and `name` is directly copied (no quotes - not JSON).
template<typename T>
void json_pub(epee::byte_stream& buf, const T value)
std::error_code json_pub(epee::byte_stream& buf, const T value)
{
rapidjson::Writer<epee::byte_stream> dest{buf};
using cryptonote::json::toJsonValue;
toJsonValue(dest, value);
return {};
}
//! Object for "minimal" block serialization
struct minimal_chain
{
WIRE_DEFINE_CONVERSIONS()
const std::uint64_t height;
const epee::span<const cryptonote::block> blocks;
};
void write_bytes(wire::writer& dest, const minimal_chain& self)
{
namespace adapt = boost::adaptors;
const auto to_block_id = [](const cryptonote::block& bl)
{
crypto::hash id;
if (!get_block_hash(bl, id))
WIRE_DLOG_THROW(error::code::bad_block_hash, "ZMQ/Pub failure");
return id;
};
assert(!self.blocks.empty()); // checked in zmq_pub::send_chain_main
wire::object(dest,
wire::field("first_height", self.height),
wire::field("first_prev_id", std::cref(self.blocks[0].prev_id)),
wire::field("ids", wire::range(self.blocks | adapt::transformed(to_block_id)))
);
}
//! Object for miner data serialization
struct miner_data
{
WIRE_DEFINE_CONVERSIONS()
uint8_t major_version;
uint64_t height;
const crypto::hash& prev_id;
@@ -132,115 +210,98 @@ namespace
uint64_t already_generated_coins;
const std::vector<cryptonote::tx_block_template_backlog_entry>& tx_backlog;
};
void write_bytes(wire::writer& dest, const miner_data& self)
{
wire::object(dest,
WIRE_FIELD_COPY(major_version),
WIRE_FIELD_COPY(height),
WIRE_FIELD(prev_id),
WIRE_FIELD(seed_hash),
wire::field("difficulty", cryptonote::hex(self.diff)),
WIRE_FIELD_COPY(median_weight),
WIRE_FIELD_COPY(already_generated_coins),
WIRE_FIELD(tx_backlog)
);
}
//! Object for "minimal" tx serialization
struct minimal_txpool
{
WIRE_DEFINE_CONVERSIONS()
const cryptonote::transaction& tx;
crypto::hash hash;
uint64_t blob_size;
uint64_t weight;
uint64_t fee;
};
void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const minimal_chain& self)
void write_bytes(wire::writer& dest, const minimal_txpool& self)
{
namespace adapt = boost::adaptors;
const auto to_block_id = [](const cryptonote::block& bl)
{
crypto::hash id;
if (!get_block_hash(bl, id))
MERROR("ZMQ/Pub failure: get_block_hash");
return id;
};
assert(!self.blocks.empty()); // checked in zmq_pub::send_chain_main
dest.StartObject();
INSERT_INTO_JSON_OBJECT(dest, first_height, self.height);
INSERT_INTO_JSON_OBJECT(dest, first_prev_id, self.blocks[0].prev_id);
INSERT_INTO_JSON_OBJECT(dest, ids, (self.blocks | adapt::transformed(to_block_id)));
dest.EndObject();
wire::object(dest,
wire::field("id", std::cref(self.hash)),
WIRE_FIELD_COPY(blob_size),
WIRE_FIELD_COPY(weight),
WIRE_FIELD_COPY(fee)
);
}
void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const miner_data& self)
std::error_code json_full_chain(epee::byte_stream& buf, const std::uint64_t height, const epee::span<const cryptonote::block> blocks)
{
dest.StartObject();
INSERT_INTO_JSON_OBJECT(dest, major_version, self.major_version);
INSERT_INTO_JSON_OBJECT(dest, height, self.height);
INSERT_INTO_JSON_OBJECT(dest, prev_id, self.prev_id);
INSERT_INTO_JSON_OBJECT(dest, seed_hash, self.seed_hash);
INSERT_INTO_JSON_OBJECT(dest, difficulty, cryptonote::hex(self.diff));
INSERT_INTO_JSON_OBJECT(dest, median_weight, self.median_weight);
INSERT_INTO_JSON_OBJECT(dest, already_generated_coins, self.already_generated_coins);
INSERT_INTO_JSON_OBJECT(dest, tx_backlog, self.tx_backlog);
dest.EndObject();
return json_pub(buf, blocks);
}
void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const minimal_txpool& self)
template<typename F>
std::error_code minimal_chain_format(epee::byte_stream& buf, const std::uint64_t height, const epee::span<const cryptonote::block> blocks)
{
dest.StartObject();
INSERT_INTO_JSON_OBJECT(dest, id, self.hash);
INSERT_INTO_JSON_OBJECT(dest, blob_size, self.blob_size);
INSERT_INTO_JSON_OBJECT(dest, weight, self.weight);
INSERT_INTO_JSON_OBJECT(dest, fee, self.fee);
dest.EndObject();
return F::to_bytes(buf, minimal_chain{height, blocks});
}
void json_full_chain(epee::byte_stream& buf, const std::uint64_t height, const epee::span<const cryptonote::block> blocks)
template<typename F>
std::error_code miner_data_format(epee::byte_stream& buf, uint8_t major_version, uint64_t height, const crypto::hash& prev_id, const crypto::hash& seed_hash, cryptonote::difficulty_type diff, uint64_t median_weight, uint64_t already_generated_coins, const std::vector<cryptonote::tx_block_template_backlog_entry>& tx_backlog)
{
json_pub(buf, blocks);
}
void json_minimal_chain(epee::byte_stream& buf, const std::uint64_t height, const epee::span<const cryptonote::block> blocks)
{
json_pub(buf, minimal_chain{height, blocks});
}
void json_miner_data(epee::byte_stream& buf, uint8_t major_version, uint64_t height, const crypto::hash& prev_id, const crypto::hash& seed_hash, cryptonote::difficulty_type diff, uint64_t median_weight, uint64_t already_generated_coins, const std::vector<cryptonote::tx_block_template_backlog_entry>& tx_backlog)
{
json_pub(buf, miner_data{major_version, height, prev_id, seed_hash, diff, median_weight, already_generated_coins, tx_backlog});
return F::to_bytes(buf, miner_data{major_version, height, prev_id, seed_hash, diff, median_weight, already_generated_coins, tx_backlog});
}
// boost::adaptors are in place "views" - no copy/move takes place
// moving transactions (via sort, etc.), is expensive!
void json_full_txpool(epee::byte_stream& buf, epee::span<const cryptonote::txpool_event> txes)
std::error_code json_full_txpool(epee::byte_stream& buf, epee::span<const cryptonote::txpool_event> txes)
{
namespace adapt = boost::adaptors;
const auto to_full_tx = [](const cryptonote::txpool_event& event)
{
return event.tx;
};
json_pub(buf, (txes | adapt::filtered(is_valid{}) | adapt::transformed(to_full_tx)));
return json_pub(buf, (txes | adapt::filtered(is_valid{}) | adapt::transformed(to_full_tx)));
}
void json_minimal_txpool(epee::byte_stream& buf, epee::span<const cryptonote::txpool_event> txes)
template<typename F>
std::error_code minimal_txpool_format(epee::byte_stream& buf, epee::span<const cryptonote::txpool_event> txes)
{
namespace adapt = boost::adaptors;
const auto to_minimal_tx = [](const cryptonote::txpool_event& event)
{
return minimal_txpool{event.tx, event.hash, event.blob_size, event.weight, cryptonote::get_tx_fee(event.tx)};
};
json_pub(buf, (txes | adapt::filtered(is_valid{}) | adapt::transformed(to_minimal_tx)));
return F::to_bytes(buf, wire::range(txes | adapt::filtered(is_valid{}) | adapt::transformed(to_minimal_tx)));
}
constexpr const std::array<context<chain_writer>, 2> chain_contexts =
{{
{u8"json-full-chain_main", json_full_chain},
{u8"json-minimal-chain_main", json_minimal_chain}
{u8"json-minimal-chain_main", minimal_chain_format<wire::json>}
}};
constexpr const std::array<context<miner_writer>, 1> miner_contexts =
{{
{u8"json-full-miner_data", json_miner_data},
{u8"json-full-miner_data", miner_data_format<wire::json>}
}};
constexpr const std::array<context<txpool_writer>, 2> txpool_contexts =
{{
{u8"json-full-txpool_add", json_full_txpool},
{u8"json-minimal-txpool_add", json_minimal_txpool}
{u8"json-minimal-txpool_add", minimal_txpool_format<wire::json>}
}};
template<typename T, std::size_t N>
@@ -288,20 +349,25 @@ namespace
epee::byte_stream buf{};
std::size_t last_offset = 0;
std::array<epee::byte_slice, N> out;
std::array<std::size_t, N> offsets{{}};
for (std::size_t i = 0; i < N; ++i)
{
if (subs[i])
{
write_header(buf, contexts[i].name);
contexts[i].generate_pub(buf, std::forward<U>(args)...);
const std::error_code error = contexts[i].generate_pub(buf, std::forward<U>(args)...);
if (error)
{
MERROR("Failed to serialize " << contexts[i].name << ": " << error.message());
return out;
}
offsets[i] = buf.size() - last_offset;
last_offset = buf.size();
}
}
epee::byte_slice bytes{std::move(buf)};
std::array<epee::byte_slice, N> out;
for (std::size_t i = 0; i < N; ++i)
out[i] = bytes.take_slice(offsets[i]);
+2 -2
View File
@@ -82,8 +82,8 @@ class zmq_pub
//! Process a client subscription request (from XPUB sockets). Thread-safe.
bool sub_request(const boost::string_ref message);
/*! Forward ZMQ messages sent to `relay` via `send_chain_main` or
`send_txpool_add` to `pub`. Used by `ZmqServer`. */
/*! Forward ZMQ messages sent to `relay` via `send_chain_main`,
`send_txpool_add`, or `send_miner_data` to `pub`. Used by `ZmqServer`. */
bool relay_to_pub(void* relay, void* pub);
/*! Send a `ZMQ_PUB` notification for a change to the main chain.
-21
View File
@@ -1532,27 +1532,6 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_distribu
GET_FROM_JSON_OBJECT(val, dist.data.base, base);
}
void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::tx_block_template_backlog_entry& entry)
{
dest.StartObject();
INSERT_INTO_JSON_OBJECT(dest, id, entry.id);
INSERT_INTO_JSON_OBJECT(dest, weight, entry.weight);
INSERT_INTO_JSON_OBJECT(dest, fee, entry.fee);
dest.EndObject();
}
void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_block_template_backlog_entry& entry)
{
if (!val.IsObject())
{
throw WRONG_TYPE("json object");
}
GET_FROM_JSON_OBJECT(val, entry.id, id);
GET_FROM_JSON_OBJECT(val, entry.weight, weight);
GET_FROM_JSON_OBJECT(val, entry.fee, fee);
}
} // namespace json
} // namespace cryptonote
-3
View File
@@ -311,9 +311,6 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::DaemonInfo& inf
void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::rpc::output_distribution& dist);
void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_distribution& dist);
void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::tx_block_template_backlog_entry& entry);
void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_block_template_backlog_entry& entry);
template <typename Map>
typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const Map& map);
+90
View File
@@ -30,11 +30,14 @@
#include <boost/preprocessor/stringize.hpp>
#include <gtest/gtest.h>
#include <rapidjson/document.h>
#include <rapidjson/ostreamwrapper.h>
#include <rapidjson/prettywriter.h>
#include "cryptonote_basic/account.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/events.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_core/cryptonote_tx_utils.h"
#include "json_serialization.h"
#include "net/zmq.h"
#include "rpc/message.h"
@@ -47,6 +50,17 @@
if (!(__VA_ARGS__)) \
return testing::AssertionFailure() << BOOST_PP_STRINGIZE(__VA_ARGS__)
namespace rapidjson
{
std::ostream& operator<<(std::ostream& out, const Document& src)
{
OStreamWrapper buffer{out};
PrettyWriter<OStreamWrapper> writer{buffer};
src.Accept(writer);
return out;
}
}
TEST(ZmqFullMessage, InvalidRequest)
{
EXPECT_THROW(
@@ -138,6 +152,16 @@ namespace
return out;
}
testing::AssertionResult compare_json(const std::string expected_json, const rapidjson::Document& published)
{
rapidjson::Document expected;
expected.Parse(expected_json.c_str());
MASSERT(!expected.HasParseError());
if (expected != published)
return testing::AssertionFailure() << expected << " != " << published;
return testing::AssertionSuccess();
}
testing::AssertionResult compare_full_txpool(epee::span<const cryptonote::txpool_event> events, const published_json& pub)
{
MASSERT(pub.first == "json-full-txpool_add");
@@ -193,6 +217,12 @@ namespace
return testing::AssertionSuccess();
}
testing::AssertionResult compare_miner_data(const std::string expected, const published_json& pub)
{
MASSERT(pub.first == "json-full-miner_data");
return compare_json(expected, pub.second);
}
testing::AssertionResult compare_full_block(const epee::span<const cryptonote::block> expected, const published_json& pub)
{
MASSERT(pub.first == "json-full-chain_main");
@@ -513,6 +543,66 @@ TEST_F(zmq_pub, JsonFullChain)
EXPECT_TRUE(compare_full_block(epee::to_span(blocks), pubs.front()));
}
TEST_F(zmq_pub, JsonFullMinerData)
{
/* uint8_t major_version;
uint64_t height;
const crypto::hash& prev_id;
const crypto::hash& seed_hash;
cryptonote::difficulty_type diff;
uint64_t median_weight;
uint64_t already_generated_coins;
const std::vector<cryptonote::tx_block_template_backlog_entry>& tx_backlog ; */
static constexpr const char topic[] = "\1json-full-miner_data";
ASSERT_TRUE(sub_request(topic));
//std::size_t send_miner_data(uint8_t major_version, uint64_t height, const crypto::hash& prev_id, const crypto::hash& seed_hash, difficulty_type diff, uint64_t median_weight, uint64_t already_generated_coins, const std::vector<tx_block_template_backlog_entry>& tx_backlog);
const auto hash = crypto::rand<crypto::hash>();
const auto seed = crypto::rand<crypto::hash>();
const cryptonote::difficulty_type difficulty = 500;
const std::vector<cryptonote::tx_block_template_backlog_entry> txs = {
{crypto::rand<crypto::hash>(), 7545, 455},
{crypto::rand<crypto::hash>(), 755, 34545}
};
const std::string expected =
R"({
"major_version": 100,
"height": 200,
"prev_id": ")" + epee::to_hex::string(epee::as_byte_span(hash)) + R"(",
"seed_hash": ")" + epee::to_hex::string(epee::as_byte_span(seed)) + R"(",
"difficulty": ")" + cryptonote::hex(difficulty) + R"(",
"median_weight": 400,
"already_generated_coins": 10000,
"tx_backlog": [
{
"id": ")" + epee::to_hex::string(epee::as_byte_span(txs.at(0).id)) + R"(",
"weight": 7545,
"fee": 455
},{
"id": ")" + epee::to_hex::string(epee::as_byte_span(txs.at(1).id)) + R"(",
"weight": 755,
"fee": 34545
}
]
})";
EXPECT_EQ(1u, pub->send_miner_data(100, 200, hash, seed, difficulty, 400, 10000, txs));
EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get()));
auto pubs = get_published(dummy_client.get());
ASSERT_EQ(1u, pubs.size());
EXPECT_TRUE(compare_miner_data(expected, pubs.front()));
EXPECT_NO_THROW(cryptonote::listener::zmq_pub::miner_data{pub}(100, 200, hash, seed, difficulty, 400, 10000, txs));
EXPECT_TRUE(pub->relay_to_pub(relay.get(), dummy_pub.get()));
pubs = get_published(dummy_client.get());
ASSERT_EQ(1u, pubs.size());
EXPECT_TRUE(compare_miner_data(expected, pubs.front()));
}
TEST_F(zmq_pub, JsonMinimalChain)
{
static constexpr const char topic[] = "\1json-minimal-chain_main";