diff options
42 files changed, 1508 insertions, 292 deletions
diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h index 8cee399cb..ecbceb566 100644 --- a/contrib/epee/include/net/http_client.h +++ b/contrib/epee/include/net/http_client.h @@ -199,8 +199,18 @@ namespace net_utils } } + // This magic var determines the maximum length for when copying the body message in + // memory is faster/more preferable than the round-trip time for one packet + constexpr size_t BODY_NO_COPY_CUTOFF = 128 * 1024; // ~262 KB or ~175 packets + + // Maximum expected total headers bytes + constexpr size_t HEADER_RESERVE_SIZE = 2048; + + const bool do_copy_body = body.size() <= BODY_NO_COPY_CUTOFF; + const size_t req_buff_cap = HEADER_RESERVE_SIZE + (do_copy_body ? body.size() : 0); + std::string req_buff{}; - req_buff.reserve(2048); + req_buff.reserve(req_buff_cap); req_buff.append(method.data(), method.size()).append(" ").append(uri.data(), uri.size()).append(" HTTP/1.1\r\n"); add_field(req_buff, "Host", m_host_buff); add_field(req_buff, "Content-Length", std::to_string(body.size())); @@ -209,9 +219,7 @@ namespace net_utils for(const auto& field : additional_params) add_field(req_buff, field); - for (unsigned sends = 0; sends < 2; ++sends) { - const std::size_t initial_size = req_buff.size(); const auto auth = m_auth.get_auth_field(method, uri); if (auth) add_field(req_buff, *auth); @@ -219,11 +227,21 @@ namespace net_utils req_buff += "\r\n"; //-- - bool res = m_net_client.send(req_buff, timeout); - CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); - if(body.size()) + if (do_copy_body) // small body + { + // Copy headers + body together and potentially send one fewer packet + req_buff.append(body.data(), body.size()); + const bool res = m_net_client.send(req_buff, timeout); + CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); + } + else // large body + { + // Send headers and body seperately to avoid copying heavy body message + bool res = m_net_client.send(req_buff, timeout); + CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); res = m_net_client.send(body, timeout); - CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); + CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); + } m_response_info.clear(); m_state = reciev_machine_state_header; @@ -236,19 +254,11 @@ namespace net_utils return true; } - switch (m_auth.handle_401(m_response_info)) + if (m_auth.handle_401(m_response_info) == http_client_auth::kParseFailure) { - case http_client_auth::kSuccess: - break; - case http_client_auth::kBadPassword: - sends = 2; - break; - default: - case http_client_auth::kParseFailure: LOG_ERROR("Bad server response for authentication"); return false; } - req_buff.resize(initial_size); // rollback for new auth generation } LOG_ERROR("Client has incorrect username/password for server requiring authentication"); return false; diff --git a/contrib/epee/include/serialization/wire.h b/contrib/epee/include/serialization/wire.h new file mode 100644 index 000000000..4dbb0b2f4 --- /dev/null +++ b/contrib/epee/include/serialization/wire.h @@ -0,0 +1,51 @@ +// Copyright (c) 2021-2023, 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 "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); } + diff --git a/contrib/epee/include/serialization/wire/adapted/list.h b/contrib/epee/include/serialization/wire/adapted/list.h new file mode 100644 index 000000000..8884193ad --- /dev/null +++ b/contrib/epee/include/serialization/wire/adapted/list.h @@ -0,0 +1,41 @@ +// Copyright (c) 2023, 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 <list> +#include <type_traits> + +#include "serialization/wire/traits.h" + +namespace wire +{ + template<typename T, typename A> + struct is_array<std::list<T, A>> + : std::true_type + {}; +} diff --git a/contrib/epee/include/serialization/wire/adapted/vector.h b/contrib/epee/include/serialization/wire/adapted/vector.h new file mode 100644 index 000000000..1dd4b0ded --- /dev/null +++ b/contrib/epee/include/serialization/wire/adapted/vector.h @@ -0,0 +1,61 @@ +// Copyright (c) 2022-2023-2023, The Monero Project +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <cstdint> +#include <cstring> +#include <type_traits> +#include <vector> + +#include "byte_slice.h" +#include "serialization/wire/traits.h" + +namespace wire +{ + template<typename T, typename A> + struct is_array<std::vector<T, A>> + : std::true_type + {}; + template<typename A> + struct is_array<std::vector<std::uint8_t, A>> + : std::false_type + {}; + + template<typename R, typename A> + inline void read_bytes(R& source, std::vector<std::uint8_t, A>& dest) + { + const epee::byte_slice bytes = source.binary(); + dest.resize(bytes.size()); + std::memcpy(dest.data(), bytes.data(), bytes.size()); + } + template<typename W, typename A> + inline void write_bytes(W& dest, const std::vector<std::uint8_t, A>& source) + { + dest.binary(epee::to_span(source)); + } +} diff --git a/contrib/epee/include/serialization/wire/error.h b/contrib/epee/include/serialization/wire/error.h new file mode 100644 index 000000000..b4920e53d --- /dev/null +++ b/contrib/epee/include/serialization/wire/error.h @@ -0,0 +1,137 @@ +// Copyright (c) 2022-2023, 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 <exception> +#include <system_error> +#include <type_traits> + +#include "misc_log_ex.h" + +//! Print default `code` message followed by optional message to debug log then throw `code`. +#define WIRE_DLOG_THROW_(code, ...) \ + do \ + { \ + MDEBUG( get_string(code) __VA_ARGS__ ); \ + throw ::wire::exception_t<decltype(code)>{code}; \ + } \ + while (0) + +//! Print default `code` message followed by `msg` to debug log then throw `code`. +#define WIRE_DLOG_THROW(code, msg) \ + WIRE_DLOG_THROW_(code, << ": " << msg) + +namespace wire +{ + namespace error + { + enum class schema : int + { + none = 0, //!< Must be zero for `expect<..>` + array, //!< Expected an array value + array_max_element,//!< Exceeded max array count + array_min_size, //!< Below min element wire size + binary, //!< Expected a binary value of variable length + boolean, //!< Expected a boolean value + enumeration, //!< Expected a value from a specific set + fixed_binary, //!< Expected a binary value of fixed length + integer, //!< Expected an integer value + invalid_key, //!< Key for object is invalid + larger_integer, //!< Expected a larger integer value + maximum_depth, //!< Hit maximum number of object+array tracking + missing_key, //!< Missing required key for object + number, //!< Expected a number (integer or float) value + object, //!< Expected object value + smaller_integer, //!< Expected a smaller integer value + string, //!< Expected string value + }; + + //! \return Error message string. + const char* get_string(schema value) noexcept; + + //! \return Category for `schema_error`. + const std::error_category& schema_category() noexcept; + + //! \return Error code with `value` and `schema_category()`. + inline std::error_code make_error_code(const schema value) noexcept + { + return std::error_code{int(value), schema_category()}; + } + } // error + + //! `std::exception` doesn't require dynamic memory like `std::runtime_error` + struct exception : std::exception + { + exception() noexcept + : std::exception() + {} + + exception(const exception&) = default; + exception& operator=(const exception&) = default; + virtual ~exception() noexcept + {} + + virtual std::error_code code() const noexcept = 0; + }; + + template<typename T> + class exception_t final : public wire::exception + { + static_assert(std::is_enum<T>(), "only enumerated types allowed"); + T value; + + public: + exception_t(T value) noexcept + : value(value) + {} + + exception_t(const exception_t&) = default; + ~exception_t() = default; + exception_t& operator=(const exception_t&) = default; + + const char* what() const noexcept override final + { + static_assert(noexcept(noexcept(get_string(value))), "get_string function must be noexcept"); + return get_string(value); + } + + std::error_code code() const noexcept override final + { + static_assert(noexcept(noexcept(make_error_code(value))), "make_error_code funcion must be noexcept"); + return make_error_code(value); + } + }; +} // wire + +namespace std +{ + template<> + struct is_error_code_enum<wire::error::schema> + : true_type + {}; +} diff --git a/contrib/epee/include/serialization/wire/field.h b/contrib/epee/include/serialization/wire/field.h new file mode 100644 index 000000000..402fa9ad4 --- /dev/null +++ b/contrib/epee/include/serialization/wire/field.h @@ -0,0 +1,141 @@ +// Copyright (c) 2021-2023, 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 <functional> +#include <utility> + +#include "serialization/wire/traits.h" + +//! A required field has the same key name and C/C++ name +#define WIRE_FIELD(name) \ + ::wire::field( #name , std::ref( self . name )) + +//! A required field has the same key name and C/C++ name AND is cheap to copy (faster output). +#define WIRE_FIELD_COPY(name) \ + ::wire::field( #name , self . name ) + +//! The optional field has the same key name and C/C++ name +#define WIRE_OPTIONAL_FIELD(name) \ + ::wire::optional_field( #name , std::ref( self . name )) + +namespace wire +{ + /*! Links `name` to a `value` for object serialization. + + `value_type` is `T` with optional `std::reference_wrapper` removed. + `value_type` needs a `read_bytes` function when parsing with a + `wire::reader` - see `read.h` for more info. `value_type` needs a + `write_bytes` function when writing with a `wire::writer` - see `write.h` + for more info. + + Any `value_type` where `is_optional_on_empty<value_type> == true`, will + automatically be converted to an optional field iff `value_type` has an + `empty()` method that returns `true`. The old output engine omitted fields + when an array was empty, and the standard input macro would ignore the + `false` return for the missing field. For compability reasons, the + input/output engine here matches that behavior. See `wrapper/array.h` to + enforce a required field even when the array is empty or specialize the + `is_optional_on_empty` trait. Only new fields should use this behavior. + + Additional concept requirements for `value_type` when `Required == false`: + * must have an `operator*()` function. + * must have a conversion to bool function that returns true when + `operator*()` is safe to call (and implicitly when the associated field + should be written as opposed to skipped/omitted). + Additional concept requirements for `value_type` when `Required == false` + when reading: + * must have an `emplace()` method that ensures `operator*()` is safe to call. + * must have a `reset()` method to indicate a field was skipped/omitted. + + If a standard type needs custom serialization, one "trick": + ``` + struct custom_tag{}; + void read_bytes(wire::reader&, boost::fusion::pair<custom_tag, std::string&>) + { ... } + void write_bytes(wire::writer&, boost::fusion::pair<custom_tag, const std::string&>) + { ... } + + template<typename F, typename T> + void object_map(F& format, T& self) + { + wire::object(format, + wire::field("foo", boost::fusion::make_pair<custom_tag>(std::ref(self.foo))) + ); + } + ``` + + Basically each input/output format needs a unique type so that the compiler + knows how to "dispatch" the read/write calls. */ + template<typename T, bool Required> + struct field_ + { + using value_type = unwrap_reference_t<T>; + + //! \return True if field is forced optional when `get_value().empty()`. + static constexpr bool optional_on_empty() noexcept + { return is_optional_on_empty<value_type>::value; } + + static constexpr bool is_required() noexcept { return Required && !optional_on_empty(); } + static constexpr std::size_t count() noexcept { return 1; } + + const char* name; + T value; + + constexpr const value_type& get_value() const noexcept { return value; } + value_type& get_value() noexcept { return value; } + }; + + //! Links `name` to `value`. Use `std::ref` if de-serializing. + template<typename T> + constexpr inline field_<T, true> field(const char* name, T value) + { + return {name, std::move(value)}; + } + + //! Links `name` to optional `value`. Use `std::ref` if de-serializing. + template<typename T> + constexpr inline field_<T, false> optional_field(const char* name, T value) + { + return {name, std::move(value)}; + } + + + template<typename T> + inline constexpr bool available(const field_<T, true>& elem) + { + /* The old output engine always skipped fields when it was an empty array, + this follows that behavior. See comments for `field_`. */ + return elem.is_required() || (elem.optional_on_empty() && !wire::empty(elem.get_value())); + } + template<typename T> + inline constexpr bool available(const field_<T, false>& elem) + { + return bool(elem.get_value()); + } +} // wire diff --git a/contrib/epee/include/serialization/wire/fwd.h b/contrib/epee/include/serialization/wire/fwd.h new file mode 100644 index 000000000..f9e79dd1a --- /dev/null +++ b/contrib/epee/include/serialization/wire/fwd.h @@ -0,0 +1,103 @@ +// Copyright (c) 2021-2023, 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> + +//! Declare an enum to be serialized as an integer +#define WIRE_AS_INTEGER(type_) \ + static_assert(std::is_enum<type_>(), "AS_INTEGER only enum types"); \ + template<typename R> \ + inline void read_bytes(R& source, type_& dest) \ + { \ + std::underlying_type<type_>::type temp{}; \ + read_bytes(source, temp); \ + dest = type_(temp); \ + } \ + template<typename W> \ + inline void write_bytes(W& dest, const type_ source) \ + { write_bytes(dest, std::underlying_type<type_>::type(source)); } + +//! Declare functions that list fields in `type` (using virtual interface) +#define WIRE_DECLARE_OBJECT(type) \ + void read_bytes(::wire::reader&, type&); \ + void write_bytes(::wire::writer&, const type&) + +//! Cast readers to `rtype` and writers to `wtype` before code expansion +#define WIRE_BEGIN_MAP_BASE(rtype, wtype) \ + template<typename R> \ + void read_bytes(R& source) \ + { wire_map(std::true_type{}, static_cast<rtype&>(source), *this); } \ + \ + template<typename W> \ + void write_bytes(W& dest) const \ + { wire_map(std::false_type{}, static_cast<wtype&>(dest), *this); } \ + \ + template<typename B, typename F, typename T> \ + static void wire_map(const B is_read, F& format, T& self) \ + { ::wire::object_fwd(is_read, format + +/*! Define `read_bytes`, and `write_bytes` for `this` that forward the + derived format types for max performance. */ +#define WIRE_BEGIN_MAP() \ + WIRE_BEGIN_MAP_BASE(R, W) + +/*! Define `read_bytes`, and `write_bytes` for `this` that forward base format + types to reduce code expansion and executable size. */ +#define WIRE_BEGIN_MAP_ASM_SIZE() \ + WIRE_BEGIN_MAP_BASE(::wire::reader, ::wire::writer) + +//! End object map; omit last `,` +#define WIRE_END_MAP() );} + +namespace wire +{ + struct basic_value; + class reader; + struct writer; + + // defined in `wire/read.h` + template<typename R, typename... T> + void object_fwd(std::true_type is_read, R& source, T&&... fields); + + // defined in `wire/write.h` + template<typename W, typename... T> + void object_fwd(std::false_type is_read, W& dest, T... fields); +} +namespace wire_read +{ + // defined in `wire/read.h` + template<typename R, typename T> + void bytes(R& source, T&& dest); +} +namespace wire_write +{ + // defined in `wire/write.h` + template<typename W, typename T> + void bytes(W& dest, const T& source); +} diff --git a/contrib/epee/include/serialization/wire/traits.h b/contrib/epee/include/serialization/wire/traits.h new file mode 100644 index 000000000..284506a29 --- /dev/null +++ b/contrib/epee/include/serialization/wire/traits.h @@ -0,0 +1,195 @@ +// 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 <cstdint> +#include <type_traits> + +#define WIRE_DECLARE_BLOB_NS(type) \ + template<> \ + struct is_blob<type> \ + : std::true_type \ + {} + +#define WIRE_DECLARE_BLOB(type) \ + namespace wire { WIRE_DECLARE_BLOB_NS(type); } + +#define WIRE_DECLARE_OPTIONAL_ROOT(type) \ + template<> \ + struct is_optional_root<type> \ + : std::true_type \ + {} + +namespace wire +{ + template<typename T> + struct unwrap_reference + { + using type = std::remove_cv_t<std::remove_reference_t<T>>; + }; + + template<typename T> + struct unwrap_reference<std::reference_wrapper<T>> + : std::remove_cv<T> + {}; + + template<typename T> + using unwrap_reference_t = typename unwrap_reference<T>::type; + + + /*! Mark `T` as an array for writing, and reading when + `default_min_element_size<T::value_type>::value != 0`. See `array_` in + `wrapper/array.h`. */ + template<typename T> + struct is_array : std::false_type + {}; + + /*! Mark `T` as fixed binary data for reading+writing. Concept requirements + for reading: + * `T` must be compatible with `epee::as_mut_byte_span` (`std::is_pod<T>` + and no padding). + Concept requirements for writing: + * `T` must be compatible with `epee::as_byte_span` (std::is_pod<T>` and + no padding). */ + template<typename T> + struct is_blob : std::false_type + {}; + + /*! Forces field to be optional when empty. Concept requirements for `T` when + `is_optional_on_empty<T>::value == true`: + * must have an `empty()` method that toggles whether the associated + `wire::field_<...>` is omitted by the `wire::writer`. + * must have a `clear()` method where `empty() == true` upon completion, + used by the `wire::reader` when the `wire::field_<...>` is omitted. */ + template<typename T> + struct is_optional_on_empty + : is_array<T> // all array types in old output engine were optional when empty + {}; + + //! When `T` is being read as root object, allow an empty read buffer. + template<typename T> + struct is_optional_root + : std::is_empty<T> + {}; + + //! A constraint for `wire_read::array` where a max of `N` elements can be read. + template<std::size_t N> + struct max_element_count + : std::integral_constant<std::size_t, N> + { + // The threshold is low - min_element_size is a better constraint metric + static constexpr std::size_t max_bytes() noexcept { return 512 * 1024; } // 512 KiB + + //! \return True if `N` C++ objects of type `T` are below `max_bytes()` threshold. + template<typename T> + static constexpr bool check() noexcept + { + return N <= (max_bytes() / sizeof(T)); + } + }; + + //! A constraint for `wire_read::array` where each element must use at least `N` bytes on the wire. + template<std::size_t N> + struct min_element_size + : std::integral_constant<std::size_t, N> + { + static constexpr std::size_t max_ratio() noexcept { return 4; } + + //! \return True if C++ object of type `T` with minimum wire size `N` is below `max_ratio()`. + template<typename T> + static constexpr bool check() noexcept + { + return N != 0 ? ((sizeof(T) / N) <= max_ratio()) : false; + } + }; + + /*! Trait used in `wire/read.h` for default `min_element_size` behavior based + on an array of `T` objects and `R` reader type. This trait can be used + instead of the `wire::array(...)` (and associated macros) functionality, as + it sets a global value. The last argument is for `enable_if`. */ + template<typename R, typename T, typename = void> + struct default_min_element_size + : std::integral_constant<std::size_t, 0> + {}; + + //! If `T` is a blob, a safe default for all formats is the size of the blob + template<typename R, typename T> + struct default_min_element_size<R, T, std::enable_if_t<is_blob<T>::value>> + : std::integral_constant<std::size_t, sizeof(T)> + {}; + + // example usage : `wire::sum(std::size_t(wire::available(fields))...)` + + inline constexpr int sum() noexcept + { + return 0; + } + template<typename T, typename... U> + inline constexpr T sum(const T head, const U... tail) noexcept + { + return head + sum(tail...); + } + + template<typename... T> + using min_element_sizeof = min_element_size<sum(sizeof(T)...)>; + + //! If container has no `reserve(0)` function, this function is used + template<typename... T> + inline void reserve(const T&...) noexcept + {} + + //! Container has `reserve(std::size_t)` function, use it + template<typename T> + inline auto reserve(T& container, const std::size_t count) -> decltype(container.reserve(count)) + { return container.reserve(count); } + + //! If `T` has no `empty()` function, this function is used + template<typename... T> + inline constexpr bool empty(const T&...) noexcept + { + static_assert(sum(is_optional_on_empty<T>::value...) == 0, "type needs empty method"); + return false; + } + + //! `T` has `empty()` function, use it + template<typename T> + inline auto empty(const T& container) -> decltype(container.empty()) + { return container.empty(); } + + //! If `T` has no `clear()` function, this function is used + template<typename... T> + inline void clear(const T&...) noexcept + { + static_assert(sum(is_optional_on_empty<T>::value...) == 0, "type needs clear method"); + } + + //! `T` has `clear()` function, use it + template<typename T> + inline auto clear(T& container) -> decltype(container.clear()) + { return container.clear(); } +} // wire diff --git a/contrib/epee/include/serialization/wire/wrapper/defaulted.h b/contrib/epee/include/serialization/wire/wrapper/defaulted.h new file mode 100644 index 000000000..f9a411c9e --- /dev/null +++ b/contrib/epee/include/serialization/wire/wrapper/defaulted.h @@ -0,0 +1,77 @@ +// Copyright (c) 2021-2023, 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 <functional> +#include <utility> + +#include "serialization/wire/field.h" +#include "serialization/wire/traits.h" + +//! An optional field that is omitted when a default value is used +#define WIRE_FIELD_DEFAULTED(name, default_) \ + ::wire::optional_field( #name , ::wire::defaulted(std::ref( self . name ), default_ )) + +namespace wire +{ + /*! A wrapper that tells `wire::writer`s to skip field generation when default + value, and tells `wire::reader`s to use default value when field not present. */ + template<typename T, typename U> + struct defaulted_ + { + using value_type = unwrap_reference_t<T>; + + T value; + U default_; + + constexpr const value_type& get_value() const noexcept { return value; } + value_type& get_value() noexcept { return value; } + + // concept requirements for optional fields + + constexpr explicit operator bool() const { return get_value() != default_; } + value_type& emplace() noexcept { return get_value(); } + + constexpr const value_type& operator*() const noexcept { return get_value(); } + value_type& operator*() noexcept { return get_value(); } + + void reset() { get_value() = default_; } + }; + + //! Links `value` with `default_`. + template<typename T, typename U> + inline constexpr defaulted_<T, U> defaulted(T value, U default_) + { + return {std::move(value), std::move(default_)}; + } + + /* read/write functions not needed since `defaulted_` meets the concept + requirements for an optional type (optional fields are handled + directly by the generic read/write code because the field name is omitted + entirely when the value is "empty"). */ +} // wire diff --git a/contrib/epee/include/serialization/wire/write.h b/contrib/epee/include/serialization/wire/write.h new file mode 100644 index 000000000..c18f7dbcc --- /dev/null +++ b/contrib/epee/include/serialization/wire/write.h @@ -0,0 +1,287 @@ +// Copyright (c) 2023, The Monero Project +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <boost/utility/string_ref.hpp> +#include <boost/range/size.hpp> +#include <cstdint> +#include <system_error> +#include <type_traits> + +#include "byte_slice.h" +#include "serialization/wire/error.h" +#include "serialization/wire/field.h" +#include "serialization/wire/traits.h" +#include "span.h" + +/* + Custom types (e.g type `type` in namespace `ns`) can define an output function by: + * `namespace wire { template<> struct is_array<ns::type> : std::true_type {}; }` + * `namespace wire { template<> struct is_blob<ns::type> : std::true_type {}; }` + * `namespace wire { void write_bytes(writer&, const ns::type&); }` + * `namespace ns { void write_bytes(wire::writer&, const type&); }` + + See `wrappers.h` for `is_array` requirements, and `traits.h` for `is_blob` + requirements. `write_bytes` function can also specify derived type for faster + output (i.e. `namespace ns { void write_bytes(wire::epee_writer&, type&); }`). + Using the derived type allows the compiler to de-virtualize and allows for + custom functions not defined by base interface. Using base interface allows + for multiple formats with minimal instruction count. */ + +namespace wire +{ + //! Interface for converting C/C++ objects to "wire" (byte) formats. + struct writer + { + writer() = default; + + virtual ~writer() noexcept; + + //! By default, insist on retrieving array size before writing array + static constexpr std::true_type need_array_size() noexcept { return{}; } + + virtual void boolean(bool) = 0; + + virtual void integer(std::intmax_t) = 0; + virtual void unsigned_integer(std::uintmax_t) = 0; + + virtual void real(double) = 0; + + virtual void string(boost::string_ref) = 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 binary_key(epee::span<const std::uint8_t>) = 0; + virtual void end_object() = 0; + + protected: + writer(const writer&) = default; + writer(writer&&) = default; + writer& operator=(const writer&) = default; + writer& operator=(writer&&) = default; + }; + + template<typename W> + inline void write_arithmetic(W& dest, const bool source) + { dest.boolean(source); } + + template<typename W> + inline void write_arithmetic(W& dest, const int source) + { dest.integer(source); } + + template<typename W> + inline void write_arithmetic(W& dest, const long source) + { dest.integer(std::intmax_t(source)); } + + template<typename W> + inline void write_arithmetic(W& dest, const long long source) + { dest.integer(std::intmax_t(source)); } + + template<typename W> + inline void write_arithmetic(W& dest, const unsigned source) + { dest.unsigned_integer(source); } + + template<typename W> + inline void write_arithmetic(W& dest, const unsigned long source) + { dest.unsigned_integer(std::uintmax_t(source)); } + + template<typename W> + inline void write_arithmetic(W& dest, const unsigned long long source) + { dest.unsigned_integer(std::uintmax_t(source));} + + template<typename W> + inline void write_arithmetic(W& dest, const double source) + { dest.real(source); } + + // Template both arguments to allow derived writer specializations + template<typename W, typename T> + inline std::enable_if_t<std::is_arithmetic<T>::value> write_bytes(W& dest, const T source) + { write_arithmetic(dest, source); } + + template<typename W> + inline void write_bytes(W& dest, const boost::string_ref source) + { dest.string(source); } + + template<typename W, typename T> + inline std::enable_if_t<is_blob<T>::value> write_bytes(W& dest, const T& source) + { dest.binary(epee::as_byte_span(source)); } + + template<typename W> + inline void write_bytes(W& dest, const epee::span<const std::uint8_t> source) + { dest.binary(source); } + + template<typename W> + inline void write_bytes(W& dest, const epee::byte_slice& source) + { write_bytes(dest, epee::to_span(source)); } + + //! Use `write_bytes(...)` method if available for `T`. + template<typename W, typename T> + inline auto write_bytes(W& dest, const T& source) -> decltype(source.write_bytes(dest)) + { return source.write_bytes(dest); } +} + +namespace wire_write +{ + /*! Don't add a function called `write_bytes` to this namespace, it will + prevent ADL lookup. ADL lookup delays the function searching until the + template is used instead of when its defined. This allows the unqualified + calls to `write_bytes` in this namespace to "find" user functions that are + declared after these functions. */ + + template<typename W, typename T> + inline void bytes(W& dest, const T& source) + { + write_bytes(dest, source); // ADL (searches every associated namespace) + } + + //! Use writer `W` to convert `source` into bytes appended to `dest`. + template<typename W, typename T, typename U> + inline std::error_code to_bytes(T& dest, const U& source) + { + try + { + W out{std::move(dest)}; + bytes(out, source); + dest = out.take_buffer(); + } + catch (const wire::exception& e) + { + dest.clear(); + return e.code(); + } + catch (...) + { + dest.clear(); + throw; + } + return {}; + } + + template<typename T> + inline std::size_t array_size(std::true_type, const T& source) + { return boost::size(source); } + + template<typename T> + inline constexpr std::size_t array_size(std::false_type, const T&) noexcept + { return 0; } + + template<typename W, typename T> + inline void array(W& dest, const T& source) + { + using value_type = typename T::value_type; + static_assert(!std::is_same<value_type, char>::value, "write array of chars as string"); + static_assert(!std::is_same<value_type, std::int8_t>::value, "write array of signed chars as binary"); + static_assert(!std::is_same<value_type, std::uint8_t>::value, "write array of unsigned chars as binary"); + + dest.start_array(array_size(dest.need_array_size(), source)); + for (const auto& elem : source) + bytes(dest, elem); + dest.end_array(); + } + + template<typename W, typename T> + inline bool field(W& dest, const wire::field_<T, true>& field) + { + // Arrays always optional, see `wire/field.h` + if (wire::available(field)) + { + dest.key(field.name); + bytes(dest, field.get_value()); + } + return true; + } + + template<typename W, typename T> + inline bool field(W& dest, const wire::field_<T, false>& field) + { + if (wire::available(field)) + { + dest.key(field.name); + bytes(dest, *field.get_value()); + } + return true; + } + + template<typename W, typename T> + inline std::enable_if_t<std::is_pod<T>::value> dynamic_object_key(W& dest, const T& source) + { + dest.binary_key(epee::as_byte_span(source)); + } + + template<typename W> + inline void dynamic_object_key(W& dest, const boost::string_ref source) + { + dest.key(source); + } + + template<typename W, typename T> + inline void dynamic_object(W& dest, const T& source) + { + dest.start_object(source.size()); + for (const auto& elem : source) + { + dynamic_object_key(dest, elem.first); + bytes(dest, elem.second); + } + dest.end_object(); + } + + template<typename W, typename... T> + inline void object(W& dest, T&&... fields) + { + dest.start_object(wire::sum(std::size_t(wire::available(fields))...)); + const bool dummy[] = {field(dest, std::forward<T>(fields))...}; + dest.end_object(); + (void)dummy; // expand into array to get 0,1,2,etc order + } +} // wire_write + +namespace wire +{ + template<typename W, typename T> + inline std::enable_if_t<is_array<T>::value> write_bytes(W& dest, const T& source) + { + wire_write::array(dest, source); + } + + template<typename W, typename... T> + inline std::enable_if_t<std::is_base_of<writer, W>::value> object(W& dest, T... fields) + { + wire_write::object(dest, std::move(fields)...); + } + + template<typename W, typename... T> + inline void object_fwd(const std::false_type /* is_read */, W& dest, T... fields) + { + wire::object(dest, std::move(fields)...); + } +} diff --git a/contrib/epee/include/span.h b/contrib/epee/include/span.h index 82936a777..bbd5d690c 100644 --- a/contrib/epee/include/span.h +++ b/contrib/epee/include/span.h @@ -147,6 +147,16 @@ namespace epee return {reinterpret_cast<const std::uint8_t*>(src.data()), src.size_bytes()}; } + //! \return `span<std::uint8_t>` from a STL compatible `src`. + template<typename T> + constexpr span<std::uint8_t> to_mut_byte_span(T& src) + { + using value_type = typename T::value_type; + static_assert(!std::is_empty<value_type>(), "empty value types will not work -> sizeof == 1"); + static_assert(!has_padding<value_type>(), "source value type may have padding"); + return {reinterpret_cast<std::uint8_t*>(src.data()), src.size() * sizeof(value_type)}; + } + //! \return `span<const std::uint8_t>` which represents the bytes at `&src`. template<typename T> span<const std::uint8_t> as_byte_span(const T& src) noexcept diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 835d6dcad..b38ec9e05 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1053,7 +1053,7 @@ public: * @brief fetch a block's already generated coins * * The subclass should return the total coins generated as of the block - * with the given height. + * with the given height, capped to a maximum value of MONEY_SUPPLY. * * If the block does not exist, the subclass should throw BLOCK_DNE * diff --git a/src/blockchain_utilities/blockchain_ancestry.cpp b/src/blockchain_utilities/blockchain_ancestry.cpp index 66dd7813b..36c17357a 100644 --- a/src/blockchain_utilities/blockchain_ancestry.cpp +++ b/src/blockchain_utilities/blockchain_ancestry.cpp @@ -37,10 +37,7 @@ #include "common/command_line.h" #include "common/varint.h" #include "cryptonote_basic/cryptonote_boost_serialization.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/blockchain_db.h" #include "version.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -449,9 +446,7 @@ int main(int argc, char* argv[]) // because unlike blockchain_storage constructor, which takes a pointer to // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); - std::unique_ptr<Blockchain> core_storage; - tx_memory_pool m_mempool(*core_storage); - core_storage.reset(new Blockchain(m_mempool)); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); BlockchainDB *db = new_db(); if (db == NULL) { @@ -472,7 +467,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, net_type); + r = core_storage->blockchain.init(db, net_type); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -716,7 +711,7 @@ int main(int argc, char* argv[]) } done: - core_storage->deinit(); + core_storage->blockchain.deinit(); if (opt_show_cache_stats) MINFO("cache: txes " << std::to_string(cached_txes*100./total_txes) diff --git a/src/blockchain_utilities/blockchain_depth.cpp b/src/blockchain_utilities/blockchain_depth.cpp index 6a06e0a96..f49211233 100644 --- a/src/blockchain_utilities/blockchain_depth.cpp +++ b/src/blockchain_utilities/blockchain_depth.cpp @@ -31,10 +31,7 @@ #include <boost/algorithm/string.hpp> #include "common/command_line.h" #include "common/varint.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/blockchain_db.h" #include "version.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -129,16 +126,8 @@ int main(int argc, char* argv[]) // Use Blockchain instead of lower-level BlockchainDB for two reasons: // 1. Blockchain has the init() method for easy setup // 2. exporter needs to use get_current_blockchain_height(), get_block_id_by_height(), get_block_by_hash() - // - // cannot match blockchain_storage setup above with just one line, - // e.g. - // Blockchain* core_storage = new Blockchain(NULL); - // because unlike blockchain_storage constructor, which takes a pointer to - // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); - std::unique_ptr<Blockchain> core_storage; - tx_memory_pool m_mempool(*core_storage); - core_storage.reset(new Blockchain(m_mempool)); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); BlockchainDB *db = new_db(); if (db == NULL) { @@ -159,7 +148,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, net_type); + r = core_storage->blockchain.init(db, net_type); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -327,7 +316,7 @@ done: LOG_PRINT_L0("Average min depth for " << start_txids.size() << " transaction(s): " << cumulative_depth/(float)depths.size()); LOG_PRINT_L0("Median min depth for " << start_txids.size() << " transaction(s): " << epee::misc_utils::median(depths)); - core_storage->deinit(); + core_storage->blockchain.deinit(); return 0; CATCH_ENTRY("Depth query error", 1); diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp index 3d7b3f61a..1f8370034 100644 --- a/src/blockchain_utilities/blockchain_export.cpp +++ b/src/blockchain_utilities/blockchain_export.cpp @@ -29,7 +29,6 @@ #include "bootstrap_file.h" #include "blocksdat_file.h" #include "common/command_line.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" #include "blockchain_db/blockchain_db.h" #include "version.h" @@ -129,16 +128,8 @@ int main(int argc, char* argv[]) // Use Blockchain instead of lower-level BlockchainDB for two reasons: // 1. Blockchain has the init() method for easy setup // 2. exporter needs to use get_current_blockchain_height(), get_block_id_by_height(), get_block_by_hash() - // - // cannot match blockchain_storage setup above with just one line, - // e.g. - // Blockchain* core_storage = new Blockchain(NULL); - // because unlike blockchain_storage constructor, which takes a pointer to - // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); - Blockchain* core_storage = NULL; - tx_memory_pool m_mempool(*core_storage); - core_storage = new Blockchain(m_mempool); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); BlockchainDB* db = new_db(); if (db == NULL) @@ -162,9 +153,9 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, opt_testnet ? cryptonote::TESTNET : opt_stagenet ? cryptonote::STAGENET : cryptonote::MAINNET); + r = core_storage->blockchain.init(db, opt_testnet ? cryptonote::TESTNET : opt_stagenet ? cryptonote::STAGENET : cryptonote::MAINNET); - if (core_storage->get_blockchain_pruning_seed() && !opt_blocks_dat) + if (core_storage->blockchain.get_blockchain_pruning_seed() && !opt_blocks_dat) { LOG_PRINT_L0("Blockchain is pruned, cannot export"); return 1; @@ -177,12 +168,12 @@ int main(int argc, char* argv[]) if (opt_blocks_dat) { BlocksdatFile blocksdat; - r = blocksdat.store_blockchain_raw(core_storage, NULL, output_file_path, block_stop); + r = blocksdat.store_blockchain_raw(&core_storage->blockchain, NULL, output_file_path, block_stop); } else { BootstrapFile bootstrap; - r = bootstrap.store_blockchain_raw(core_storage, NULL, output_file_path, block_start, block_stop); + r = bootstrap.store_blockchain_raw(&core_storage->blockchain, NULL, output_file_path, block_start, block_stop); } CHECK_AND_ASSERT_MES(r, 1, "Failed to export blockchain raw data"); LOG_PRINT_L0("Blockchain raw data exported OK"); diff --git a/src/blockchain_utilities/blockchain_prune.cpp b/src/blockchain_utilities/blockchain_prune.cpp index 1e4b48b73..1a9618617 100644 --- a/src/blockchain_utilities/blockchain_prune.cpp +++ b/src/blockchain_utilities/blockchain_prune.cpp @@ -35,8 +35,6 @@ #include "common/command_line.h" #include "common/pruning.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #include "version.h" @@ -562,22 +560,15 @@ int main(int argc, char* argv[]) // Use Blockchain instead of lower-level BlockchainDB for two reasons: // 1. Blockchain has the init() method for easy setup // 2. exporter needs to use get_current_blockchain_height(), get_block_id_by_height(), get_block_by_hash() - // - // cannot match blockchain_storage setup above with just one line, - // e.g. - // Blockchain* core_storage = new Blockchain(NULL); - // because unlike blockchain_storage constructor, which takes a pointer to - // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. MINFO("Initializing source blockchain (BlockchainDB)"); - std::array<std::unique_ptr<Blockchain>, 2> core_storage; - Blockchain *blockchain = NULL; - tx_memory_pool m_mempool(*blockchain); + std::array<std::unique_ptr<BlockchainAndPool>, 2> core_storage{ + std::make_unique<BlockchainAndPool>(), + std::make_unique<BlockchainAndPool>()}; + boost::filesystem::path paths[2]; bool already_pruned = false; for (size_t n = 0; n < core_storage.size(); ++n) { - core_storage[n].reset(new Blockchain(m_mempool)); - BlockchainDB* db = new_db(); if (db == NULL) { @@ -622,12 +613,12 @@ int main(int argc, char* argv[]) MERROR("Error opening database: " << e.what()); return 1; } - r = core_storage[n]->init(db, net_type); + r = core_storage[n]->blockchain.init(db, net_type); std::string source_dest = n == 0 ? "source" : "pruned"; CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize " << source_dest << " blockchain storage"); MINFO(source_dest << " blockchain storage initialized OK"); - if (n == 0 && core_storage[0]->get_blockchain_pruning_seed()) + if (n == 0 && core_storage[0]->blockchain.get_blockchain_pruning_seed()) { if (!opt_copy_pruned_database) { @@ -637,9 +628,9 @@ int main(int argc, char* argv[]) already_pruned = true; } } - core_storage[0]->deinit(); + core_storage[0]->blockchain.deinit(); core_storage[0].reset(NULL); - core_storage[1]->deinit(); + core_storage[1]->blockchain.deinit(); core_storage[1].reset(NULL); MINFO("Pruning..."); diff --git a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp index 4da9c15c1..4a459dc66 100644 --- a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp +++ b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp @@ -30,10 +30,7 @@ #include <boost/filesystem.hpp> #include "common/command_line.h" #include "serialization/crypto.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/blockchain_db.h" #include "version.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -160,9 +157,8 @@ int main(int argc, char* argv[]) const std::string input = command_line::get_arg(vm, arg_input); LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); - std::unique_ptr<Blockchain> core_storage; - tx_memory_pool m_mempool(*core_storage); - core_storage.reset(new Blockchain(m_mempool)); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); + BlockchainDB *db = new_db(); if (db == NULL) { @@ -182,7 +178,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, net_type); + r = core_storage->blockchain.init(db, net_type); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -280,7 +276,7 @@ int main(int argc, char* argv[]) MINFO("Prunable outputs: " << num_prunable_outputs); LOG_PRINT_L0("Blockchain known spent data pruned OK"); - core_storage->deinit(); + core_storage->blockchain.deinit(); return 0; CATCH_ENTRY("Error", 1); diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index 5e4245ebd..f65054fc5 100644 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -31,9 +31,7 @@ #include "common/command_line.h" #include "common/varint.h" #include "cryptonote_basic/cryptonote_boost_serialization.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" #include "blockchain_db/blockchain_db.h" #include "version.h" @@ -203,9 +201,8 @@ int main(int argc, char* argv[]) do_diff = command_line::get_arg(vm, arg_diff); LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); - std::unique_ptr<Blockchain> core_storage; - tx_memory_pool m_mempool(*core_storage); - core_storage.reset(new Blockchain(m_mempool)); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); + BlockchainDB *db = new_db(); if (db == NULL) { @@ -225,7 +222,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, net_type); + r = core_storage->blockchain.init(db, net_type); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -381,7 +378,7 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' if (currblks) doprint(); - core_storage->deinit(); + core_storage->blockchain.deinit(); return 0; CATCH_ENTRY("Stats reporting error", 1); diff --git a/src/blockchain_utilities/blockchain_usage.cpp b/src/blockchain_utilities/blockchain_usage.cpp index a5228eb92..0b9686765 100644 --- a/src/blockchain_utilities/blockchain_usage.cpp +++ b/src/blockchain_utilities/blockchain_usage.cpp @@ -31,10 +31,7 @@ #include <boost/filesystem/path.hpp> #include "common/command_line.h" #include "common/varint.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/blockchain_db.h" #include "version.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -151,9 +148,8 @@ int main(int argc, char* argv[]) // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); const std::string input = command_line::get_arg(vm, arg_input); - std::unique_ptr<Blockchain> core_storage; - tx_memory_pool m_mempool(*core_storage); - core_storage.reset(new Blockchain(m_mempool)); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); + BlockchainDB* db = new_db(); if (db == NULL) { @@ -174,7 +170,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, net_type); + r = core_storage->blockchain.init(db, net_type); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -185,10 +181,10 @@ int main(int argc, char* argv[]) std::unordered_map<uint64_t,uint64_t> indices; LOG_PRINT_L0("Reading blockchain from " << input); - core_storage->for_all_transactions([&](const crypto::hash &hash, const cryptonote::transaction &tx)->bool + core_storage->blockchain.for_all_transactions([&](const crypto::hash &hash, const cryptonote::transaction &tx)->bool { const bool coinbase = tx.vin.size() == 1 && tx.vin[0].type() == typeid(txin_gen); - const uint64_t height = core_storage->get_db().get_tx_block_height(hash); + const uint64_t height = core_storage->blockchain.get_db().get_tx_block_height(hash); // create new outputs for (const auto &out: tx.vout) diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index a45d3ec60..3ad051fc3 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -113,13 +113,6 @@ namespace cryptonote }; /** - * @brief Blockchain constructor - * - * @param tx_pool a reference to the transaction pool to be kept by the Blockchain - */ - Blockchain(tx_memory_pool& tx_pool); - - /** * @brief Blockchain destructor */ ~Blockchain(); @@ -1236,6 +1229,13 @@ namespace cryptonote mutable rct_ver_cache_t m_rct_ver_cache; /** + * @brief Blockchain constructor + * + * @param tx_pool a reference to the transaction pool to be kept by the Blockchain + */ + Blockchain(tx_memory_pool& tx_pool); + + /** * @brief collects the keys for all outputs being "spent" as an input * * This function makes sure that each "input" in an input (mixins) exists @@ -1608,5 +1608,7 @@ namespace cryptonote * @param already_generated_coins total coins mined by the network so far */ void send_miner_notifications(uint64_t height, const crypto::hash &seed_hash, const crypto::hash &prev_id, uint64_t already_generated_coins); + + friend class BlockchainAndPool; }; } // namespace cryptonote diff --git a/src/cryptonote_core/blockchain_and_pool.h b/src/cryptonote_core/blockchain_and_pool.h new file mode 100644 index 000000000..c0f607f64 --- /dev/null +++ b/src/cryptonote_core/blockchain_and_pool.h @@ -0,0 +1,58 @@ +// Copyright (c) 2023, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list +// of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <memory> + +#include "blockchain.h" +#include "tx_pool.h" + +namespace cryptonote +{ +/** + * @brief Container for safely constructing Blockchain and tx_memory_pool classes + * + * The reason for this class existing is that the constructors for both Blockchain and + * tx_memory_pool take a reference for tx_memory_pool and Blockchain, respectively. Because of this + * circular reference, it is annoying/unsafe to construct these normally. This class guarantees that + * we don't make any silly mistakes with pointers / dangling references. + */ +struct BlockchainAndPool +{ + Blockchain blockchain; + tx_memory_pool tx_pool; + + BlockchainAndPool(): blockchain(tx_pool), tx_pool(blockchain) {} +}; +} diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 7b0c9e495..a5a59c892 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -221,8 +221,9 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- core::core(i_cryptonote_protocol* pprotocol): - m_mempool(m_blockchain_storage), - m_blockchain_storage(m_mempool), + m_bap(), + m_mempool(m_bap.tx_pool), + m_blockchain_storage(m_bap.blockchain), m_miner(this, [this](const cryptonote::block &b, uint64_t height, const crypto::hash *seed_hash, unsigned int threads, crypto::hash &hash) { return cryptonote::get_block_longhash(&m_blockchain_storage, b, hash, height, seed_hash, threads); }), @@ -1558,7 +1559,8 @@ namespace cryptonote return false; } m_blockchain_storage.add_new_block(b, bvc); - cleanup_handle_incoming_blocks(true); + const bool force_sync = m_nettype != FAKECHAIN; + cleanup_handle_incoming_blocks(force_sync); //anyway - update miner template update_miner_block_template(); m_miner.resume(); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index e0655dfa2..8108dfae0 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -42,8 +42,7 @@ #include "cryptonote_protocol/enums.h" #include "common/download.h" #include "common/command_line.h" -#include "tx_pool.h" -#include "blockchain.h" +#include "blockchain_and_pool.h" #include "cryptonote_basic/miner.h" #include "cryptonote_basic/connection_context.h" #include "warnings.h" @@ -1098,8 +1097,9 @@ namespace cryptonote uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so - tx_memory_pool m_mempool; //!< transaction pool instance - Blockchain m_blockchain_storage; //!< Blockchain instance + BlockchainAndPool m_bap; //! Contains owned instances of Blockchain and tx_memory_pool + tx_memory_pool& m_mempool; //!< ref to transaction pool instance in m_bap + Blockchain& m_blockchain_storage; //!< ref to Blockchain instance in m_bap i_cryptonote_protocol* m_pprotocol; //!< cryptonote protocol instance diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 6fe2eea59..47268efb6 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -98,14 +98,6 @@ namespace cryptonote { public: /** - * @brief Constructor - * - * @param bchs a Blockchain class instance, for getting chain info - */ - tx_memory_pool(Blockchain& bchs); - - - /** * @copydoc add_tx(transaction&, tx_verification_context&, bool, bool, uint8_t) * * @param id the transaction's hash @@ -489,6 +481,13 @@ namespace cryptonote private: /** + * @brief Constructor + * + * @param bchs a Blockchain class instance, for getting chain info + */ + tx_memory_pool(Blockchain& bchs); + + /** * @brief insert key images into m_spent_key_images * * @return true on success, false on error @@ -676,6 +675,8 @@ private: //! Next timestamp that a DB check for relayable txes is allowed std::atomic<time_t> m_next_check; + + friend class BlockchainAndPool; }; } diff --git a/src/net/tor_address.cpp b/src/net/tor_address.cpp index 53b73a839..ad8b399c8 100644 --- a/src/net/tor_address.cpp +++ b/src/net/tor_address.cpp @@ -48,7 +48,6 @@ namespace net constexpr const char tld[] = u8".onion"; constexpr const char unknown_host[] = "<unknown tor host>"; - constexpr const unsigned v2_length = 16; constexpr const unsigned v3_length = 56; constexpr const char base32_alphabet[] = @@ -62,7 +61,7 @@ namespace net host.remove_suffix(sizeof(tld) - 1); //! \TODO v3 has checksum, base32 decoding is required to verify it - if (host.size() != v2_length && host.size() != v3_length) + if (host.size() != v3_length) return {net::error::invalid_tor_address}; if (host.find_first_not_of(base32_alphabet) != boost::string_ref::npos) return {net::error::invalid_tor_address}; @@ -118,7 +117,6 @@ namespace net if (!port.empty() && !epee::string_tools::get_xtype_from_string(porti, std::string{port})) return {net::error::invalid_port}; - static_assert(v2_length <= v3_length, "bad internal host size"); static_assert(v3_length + sizeof(tld) == sizeof(tor_address::host_), "bad internal host size"); return tor_address{host, porti}; } @@ -180,7 +178,6 @@ namespace net bool tor_address::is_same_host(const tor_address& rhs) const noexcept { - //! \TODO v2 and v3 should be comparable - requires base32 return std::strcmp(host_str(), rhs.host_str()) == 0; } diff --git a/src/net/tor_address.h b/src/net/tor_address.h index 3dd320b5d..d04bf5145 100644 --- a/src/net/tor_address.h +++ b/src/net/tor_address.h @@ -71,7 +71,7 @@ namespace net static tor_address unknown() noexcept { return tor_address{}; } /*! - Parse `address` in onion v2 or v3 format with (i.e. x.onion:80) + Parse `address` in onion v3 format with (i.e. x.onion:80) with `default_port` being used iff port is not specified in `address`. */ diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 0adf0b65e..03e9ec494 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2216,7 +2216,8 @@ namespace cryptonote // Fixing of high orphan issue for most pools // Thanks Boolberry! block b; - if(!parse_and_validate_block_from_blob(blockblob, b)) + crypto::hash blk_id; + if(!parse_and_validate_block_from_blob(blockblob, b, blk_id)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; error_resp.message = "Wrong block blob"; @@ -2239,6 +2240,7 @@ namespace cryptonote error_resp.message = "Block not accepted"; return false; } + res.block_id = epee::string_tools::pod_to_hex(blk_id); res.status = CORE_RPC_STATUS_OK; return true; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 7f1581d0c..819d77c1f 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -88,7 +88,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 3 -#define CORE_RPC_VERSION_MINOR 12 +#define CORE_RPC_VERSION_MINOR 13 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -1115,8 +1115,11 @@ namespace cryptonote struct response_t: public rpc_response_base { + std::string block_id; + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(block_id) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index e41a66d24..6e5d0b1ec 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -3186,6 +3186,8 @@ simple_wallet::simple_wallet() " decrypt: same as action, but keeps the spend key encrypted in memory when not needed\n " "unit <monero|millinero|micronero|nanonero|piconero>\n " " Set the default monero (sub-)unit.\n " + "max-reorg-depth <unsigned int>\n " + " Set the maximum amount of blocks to accept in a reorg.\n " "min-outputs-count [n]\n " " Try to keep at least that many outputs of value at least min-outputs-value.\n " "min-outputs-value [n]\n " @@ -3224,6 +3226,8 @@ simple_wallet::simple_wallet() " Device name for hardware wallet.\n " "export-format <\"binary\"|\"ascii\">\n " " Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n " + "load-deprecated-formats <1|0>\n " + " Whether to enable importing data in deprecated formats.\n " "show-wallet-name-when-locked <1|0>\n " " Set this if you would like to display the wallet name when locked.\n " "enable-multisig-experimental <1|0>\n " diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f5235dbaf..ac657cf49 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -986,6 +986,21 @@ bool get_pruned_tx(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry &entry, return false; } +// Given M (threshold) and N (total), calculate the number of private multisig keys each +// signer should have. This value is equal to (N - 1) choose (N - M) +// Prereq: M >= 1 && N >= M && N <= 16 +uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total) +{ + THROW_WALLET_EXCEPTION_IF(threshold < 1 || total < threshold || threshold > 16, + tools::error::wallet_internal_error, "Invalid arguments to num_priv_multisig_keys_post_setup"); + + uint64_t n_multisig_keys = 1; + for (uint64_t i = 2; i <= total - 1; ++i) n_multisig_keys *= i; // multiply by (N - 1)! + for (uint64_t i = 2; i <= total - threshold; ++i) n_multisig_keys /= i; // divide by (N - M)! + for (uint64_t i = 2; i <= threshold - 1; ++i) n_multisig_keys /= i; // divide by ((N - 1) - (N - M))! + return n_multisig_keys; +} + //----------------------------------------------------------------- } //namespace @@ -1394,7 +1409,7 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase, bool raw) const +bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase) const { bool ready; uint32_t threshold, total; @@ -1408,15 +1423,14 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl std::cout << "This multisig wallet is not yet finalized" << std::endl; return false; } - if (!raw && seed_language.empty()) - { - std::cout << "seed_language not set" << std::endl; - return false; - } + + const uint64_t num_expected_ms_keys = num_priv_multisig_keys_post_setup(threshold, total); crypto::secret_key skey; crypto::public_key pkey; const account_keys &keys = get_account().get_keys(); + THROW_WALLET_EXCEPTION_IF(num_expected_ms_keys != keys.m_multisig_keys.size(), + error::wallet_internal_error, "Unexpected number of private multisig keys") epee::wipeable_string data; data.append((const char*)&threshold, sizeof(uint32_t)); data.append((const char*)&total, sizeof(uint32_t)); @@ -1441,18 +1455,7 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl data = encrypt(data, key, true); } - if (raw) - { - seed = epee::to_hex::wipeable_string({(const unsigned char*)data.data(), data.size()}); - } - else - { - if (!crypto::ElectrumWords::bytes_to_words(data.data(), data.size(), seed, seed_language)) - { - std::cout << "Failed to encode seed"; - return false; - } - } + seed = epee::to_hex::wipeable_string({(const unsigned char*)data.data(), data.size()}); return true; } @@ -5212,9 +5215,11 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& offset += sizeof(uint32_t); uint32_t total = *(uint32_t*)(multisig_data.data() + offset); offset += sizeof(uint32_t); - THROW_WALLET_EXCEPTION_IF(threshold < 2, error::invalid_multisig_seed); - THROW_WALLET_EXCEPTION_IF(total != threshold && total != threshold + 1, error::invalid_multisig_seed); - const size_t n_multisig_keys = total == threshold ? 1 : threshold; + + THROW_WALLET_EXCEPTION_IF(threshold < 1, error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(total < threshold, error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(threshold > 16, error::invalid_multisig_seed); // doing N choose (N - M + 1) might overflow + const uint64_t n_multisig_keys = num_priv_multisig_keys_post_setup(threshold, total); THROW_WALLET_EXCEPTION_IF(multisig_data.size() != 8 + 32 * (4 + n_multisig_keys + total), error::invalid_multisig_seed); std::vector<crypto::secret_key> multisig_keys; @@ -10217,7 +10222,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp else { LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); - while (needed_fee > test_ptx.fee) { + do { if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); @@ -10228,7 +10233,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); - } + } while (needed_fee > test_ptx.fee); LOG_PRINT_L2("Made a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -10624,7 +10629,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); do { - LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); + LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); // distribute total transferred amount between outputs uint64_t amount_transferred = available_for_fee - needed_fee; uint64_t dt_amount = amount_transferred / outputs; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 9c310f692..684bbd1ec 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1044,7 +1044,7 @@ private: bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool has_multisig_partial_key_images() const; bool has_unknown_key_images() const; - bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; + bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string()) const; bool key_on_device() const { return get_device_type() != hw::device::device_type::SOFTWARE; } hw::device::device_type get_device_type() const { return m_key_device_type; } bool reconnect_device(); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 7c46d9887..0cc42ae3f 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -3811,7 +3811,7 @@ namespace tools std::string old_language; // check the given seed - { + if (!req.enable_multisig_experimental) { if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, old_language)) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -3834,6 +3834,13 @@ namespace tools // process seed_offset if given { + if (req.enable_multisig_experimental && !req.seed_offset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Multisig seeds are not compatible with seed offsets"; + return false; + } + if (!req.seed_offset.empty()) { recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset); @@ -3897,7 +3904,27 @@ namespace tools crypto::secret_key recovery_val; try { - recovery_val = wal->generate(wallet_file, std::move(rc.second).password(), recovery_key, true, false, false); + if (req.enable_multisig_experimental) + { + // Parse multisig seed into raw multisig data + epee::wipeable_string multisig_data; + multisig_data.resize(req.seed.size() / 2); + if (!epee::from_hex::to_buffer(epee::to_mut_byte_span(multisig_data), req.seed)) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Multisig seed not represented as hexadecimal string"; + return false; + } + + // Generate multisig wallet + wal->generate(wallet_file, std::move(rc.second).password(), multisig_data, false); + wal->enable_multisig(true); + } + else + { + // Generate normal wallet + recovery_val = wal->generate(wallet_file, std::move(rc.second).password(), recovery_key, true, false, false); + } MINFO("Wallet has been restored.\n"); } catch (const std::exception &e) @@ -3908,7 +3935,7 @@ namespace tools // // Convert the secret key back to seed epee::wipeable_string electrum_words; - if (!crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language)) + if (!req.enable_multisig_experimental && !crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language)) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "Failed to encode seed"; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 002db4289..c00271030 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -2262,6 +2262,7 @@ namespace wallet_rpc std::string password; std::string language; bool autosave_current; + bool enable_multisig_experimental; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_OPT(restore_height, (uint64_t)0) @@ -2271,6 +2272,7 @@ namespace wallet_rpc KV_SERIALIZE(password) KV_SERIALIZE(language) KV_SERIALIZE_OPT(autosave_current, true) + KV_SERIALIZE_OPT(enable_multisig_experimental, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; diff --git a/tests/block_weight/block_weight.cpp b/tests/block_weight/block_weight.cpp index 7cd0d572b..4b00fc63f 100644 --- a/tests/block_weight/block_weight.cpp +++ b/tests/block_weight/block_weight.cpp @@ -30,8 +30,6 @@ #include <stdio.h> #include <math.h> -#include "cryptonote_core/blockchain.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" #include "blockchain_db/testdb.h" @@ -110,9 +108,6 @@ private: } #define PREFIX_WINDOW(hf_version,window) \ - std::unique_ptr<cryptonote::Blockchain> bc; \ - cryptonote::tx_memory_pool txpool(*bc); \ - bc.reset(new cryptonote::Blockchain(txpool)); \ struct get_test_options { \ const std::pair<uint8_t, uint64_t> hard_forks[3]; \ const cryptonote::test_options test_options = { \ @@ -121,7 +116,9 @@ private: }; \ get_test_options(): hard_forks{std::make_pair(1, (uint64_t)0), std::make_pair((uint8_t)hf_version, (uint64_t)LONG_TERM_BLOCK_WEIGHT_WINDOW), std::make_pair((uint8_t)0, (uint64_t)0)} {} \ } opts; \ - cryptonote::Blockchain *blockchain = bc.get(); \ + cryptonote::BlockchainAndPool bap; \ + cryptonote::Blockchain *blockchain = &bap.blockchain; \ + cryptonote::Blockchain *bc = blockchain; \ bool r = blockchain->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \ if (!r) \ { \ diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 9f6fe5387..b66aa7924 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -143,9 +143,8 @@ namespace } -static std::unique_ptr<cryptonote::Blockchain> init_blockchain(const std::vector<test_event_entry> & events, cryptonote::network_type nettype) +static std::unique_ptr<cryptonote::BlockchainAndPool> init_blockchain(const std::vector<test_event_entry> & events, cryptonote::network_type nettype) { - std::unique_ptr<cryptonote::Blockchain> bc; v_hardforks_t hardforks; cryptonote::test_options test_options_tmp{nullptr, 0}; const cryptonote::test_options * test_options = &test_options_tmp; @@ -159,10 +158,8 @@ static std::unique_ptr<cryptonote::Blockchain> init_blockchain(const std::vector test_options_tmp.hard_forks = hardforks.data(); test_options = &test_options_tmp; - cryptonote::tx_memory_pool txpool(*bc); - bc.reset(new cryptonote::Blockchain(txpool)); + std::unique_ptr<cryptonote::BlockchainAndPool> bap(new BlockchainAndPool()); - cryptonote::Blockchain *blockchain = bc.get(); auto bdb = new TestDB(); BOOST_FOREACH(const test_event_entry &ev, events) @@ -177,9 +174,9 @@ static std::unique_ptr<cryptonote::Blockchain> init_blockchain(const std::vector bdb->add_block(*blk, 1, 1, 1, 0, 0, blk_hash); } - bool r = blockchain->init(bdb, nettype, true, test_options, 2, nullptr); + bool r = bap->blockchain.init(bdb, nettype, true, test_options, 2, nullptr); CHECK_AND_ASSERT_THROW_MES(r, "could not init blockchain from events"); - return bc; + return bap; } void test_generator::get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const @@ -393,7 +390,7 @@ bool test_generator::construct_block_manually_tx(cryptonote::block& blk, const c void test_generator::fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t height) { const cryptonote::Blockchain *blockchain = nullptr; - std::unique_ptr<cryptonote::Blockchain> bc; + std::unique_ptr<cryptonote::BlockchainAndPool> bap; if (blk.major_version >= RX_BLOCK_VERSION && diffic > 1) { @@ -403,8 +400,8 @@ void test_generator::fill_nonce(cryptonote::block& blk, const difficulty_type& d } else { - bc = init_blockchain(*m_events, m_nettype); - blockchain = bc.get(); + bap = init_blockchain(*m_events, m_nettype); + blockchain = &bap->blockchain; } } diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py index e98037811..242c58dbe 100755 --- a/tests/functional_tests/mining.py +++ b/tests/functional_tests/mining.py @@ -36,6 +36,7 @@ import math import monotonic import util_resources import multiprocessing +import string """Test daemon mining RPC calls @@ -52,6 +53,11 @@ Control the behavior with these environment variables: from framework.daemon import Daemon from framework.wallet import Wallet +def assert_non_null_hash(s): + assert len(s) == 64 # correct length + assert all((c in string.hexdigits for c in s)) # is parseable as hex + assert s != ('0' * 64) # isn't null hash + class MiningTest(): def run_test(self): self.reset() @@ -250,6 +256,8 @@ class MiningTest(): block_hash = hashes[i] assert len(block_hash) == 64 res = daemon.submitblock(blocks[i]) + submitted_block_id = res.block_id + assert_non_null_hash(submitted_block_id) res = daemon.get_height() assert res.height == height + i + 1 assert res.hash == block_hash @@ -346,6 +354,8 @@ class MiningTest(): t0 = time.time() for h in range(len(block_hashes)): res = daemon.submitblock(blocks[h]) + submitted_block_id = res.block_id + assert_non_null_hash(submitted_block_id) t0 = time.time() - t0 res = daemon.get_info() assert height == res.height diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py index 0ca438857..7d3513070 100755 --- a/tests/functional_tests/multisig.py +++ b/tests/functional_tests/multisig.py @@ -29,6 +29,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function +import random """Test multisig transfers """ @@ -36,42 +37,74 @@ from __future__ import print_function from framework.daemon import Daemon from framework.wallet import Wallet +MULTISIG_PUB_ADDRS = [ + '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG', # 2/2 + '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i', # 2/3 + '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP', # 3/3 + '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff', # 3/4 + '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U', # 2/4 + '4A8RnBQixry4VXkqeWhmg8L7vWJVDJj4FN9PV4E7Mgad5ZZ6LKQdn8dYJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ4S8RSB' # 1/2 +] + class MultisigTest(): def run_test(self): self.reset() - self.mine('45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG', 5) - self.mine('44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i', 5) - self.mine('41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP', 5) - self.mine('44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff', 5) - self.mine('47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U', 5) + for pub_addr in MULTISIG_PUB_ADDRS: + self.mine(pub_addr, 4) self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) self.test_states() + self.fund_addrs_with_normal_wallet(MULTISIG_PUB_ADDRS) + self.create_multisig_wallets(2, 2, '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG') self.import_multisig_info([1, 0], 5) txid = self.transfer([1, 0]) self.import_multisig_info([0, 1], 6) self.check_transaction(txid) + self.remake_some_multisig_wallets_by_multsig_seed(2) + self.import_multisig_info([0, 1], 6) # six outputs, same as before + txid = self.transfer([0, 1]) + self.import_multisig_info([0, 1], 7) # seven outputs b/c we're dest plus change + self.check_transaction(txid) + self.create_multisig_wallets(2, 3, '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i') self.import_multisig_info([0, 2], 5) txid = self.transfer([0, 2]) self.import_multisig_info([0, 1, 2], 6) self.check_transaction(txid) + self.remake_some_multisig_wallets_by_multsig_seed(2) + self.import_multisig_info([0, 2], 6) # six outputs, same as before + txid = self.transfer([0, 2]) + self.import_multisig_info([0, 1, 2], 7) # seven outputs b/c we're dest plus change + self.check_transaction(txid) + self.create_multisig_wallets(3, 3, '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP') self.import_multisig_info([2, 0, 1], 5) txid = self.transfer([2, 1, 0]) self.import_multisig_info([0, 2, 1], 6) self.check_transaction(txid) + self.remake_some_multisig_wallets_by_multsig_seed(3) + self.import_multisig_info([2, 0, 1], 6) # six outputs, same as before + txid = self.transfer([2, 1, 0]) + self.import_multisig_info([0, 2, 1], 7) # seven outputs b/c we're dest plus change + self.check_transaction(txid) + self.create_multisig_wallets(3, 4, '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff') self.import_multisig_info([0, 2, 3], 5) txid = self.transfer([0, 2, 3]) self.import_multisig_info([0, 1, 2, 3], 6) self.check_transaction(txid) + self.remake_some_multisig_wallets_by_multsig_seed(3) + self.import_multisig_info([0, 2, 3], 6) # six outputs, same as before + txid = self.transfer([0, 2, 3]) + self.import_multisig_info([0, 1, 2, 3], 7) # seven outputs b/c we're dest plus change + self.check_transaction(txid) + self.create_multisig_wallets(2, 4, '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U') self.import_multisig_info([1, 2], 5) txid = self.transfer([1, 2]) @@ -81,6 +114,24 @@ class MultisigTest(): self.import_multisig_info([0, 1, 2, 3], 7) self.check_transaction(txid) + self.remake_some_multisig_wallets_by_multsig_seed(2) + self.import_multisig_info([0, 1, 2, 3], 6) # six outputs, same as before + txid = self.transfer([2, 3]) + self.import_multisig_info([0, 1, 2, 3], 7) # seven outputs b/c we're dest plus change + self.check_transaction(txid) + + self.create_multisig_wallets(1, 2, '4A8RnBQixry4VXkqeWhmg8L7vWJVDJj4FN9PV4E7Mgad5ZZ6LKQdn8dYJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ4S8RSB') + self.import_multisig_info([0, 1], 5) + txid = self.transfer([0]) + self.import_multisig_info([0, 1], 6) + self.check_transaction(txid) + + self.remake_some_multisig_wallets_by_multsig_seed(1) + self.import_multisig_info([0, 1], 6) # six outputs, same as before + txid = self.transfer([1]) + self.import_multisig_info([0, 1], 7) # seven outputs b/c we're dest plus change + self.check_transaction(txid) + def reset(self): print('Resetting blockchain') daemon = Daemon() @@ -93,6 +144,11 @@ class MultisigTest(): daemon = Daemon() daemon.generateblocks(address, blocks) + # This method sets up N_total wallets with a threshold of M_threshold doing the following steps: + # * restore_deterministic_wallet(w/ hardcoded seeds) + # * prepare_multisig(enable_multisig_experimental = True) + # * make_multisig() + # * exchange_multisig_keys() def create_multisig_wallets(self, M_threshold, N_total, expected_address): print('Creating ' + str(M_threshold) + '/' + str(N_total) + ' multisig wallet') seeds = [ @@ -103,6 +159,8 @@ class MultisigTest(): ] assert M_threshold <= N_total assert N_total <= len(seeds) + + # restore_deterministic_wallet() & prepare_multisig() self.wallet = [None] * N_total info = [] for i in range(N_total): @@ -114,10 +172,12 @@ class MultisigTest(): assert len(res.multisig_info) > 0 info.append(res.multisig_info) + # Assert that all wallets are multisig for i in range(N_total): res = self.wallet[i].is_multisig() assert res.multisig == False + # make_multisig() with each other's info addresses = [] next_stage = [] for i in range(N_total): @@ -125,6 +185,7 @@ class MultisigTest(): addresses.append(res.address) next_stage.append(res.multisig_info) + # Assert multisig paramaters M/N for each wallet for i in range(N_total): res = self.wallet[i].is_multisig() assert res.multisig == True @@ -132,13 +193,15 @@ class MultisigTest(): assert res.threshold == M_threshold assert res.total == N_total - while True: + # exchange_multisig_keys() + num_exchange_multisig_keys_stages = 0 + while True: # while not all wallets are ready n_ready = 0 for i in range(N_total): res = self.wallet[i].is_multisig() if res.ready == True: n_ready += 1 - assert n_ready == 0 or n_ready == N_total + assert n_ready == 0 or n_ready == N_total # No partial readiness if n_ready == N_total: break info = next_stage @@ -148,10 +211,17 @@ class MultisigTest(): res = self.wallet[i].exchange_multisig_keys(info) next_stage.append(res.multisig_info) addresses.append(res.address) + num_exchange_multisig_keys_stages += 1 + + # We should only need N - M + 1 key exchange rounds + assert num_exchange_multisig_keys_stages == N_total - M_threshold + 1 + + # Assert that the all wallets have expected public address for i in range(N_total): - assert addresses[i] == expected_address + assert addresses[i] == expected_address, addresses[i] self.wallet_address = expected_address + # Assert multisig paramaters M/N and "ready" for each wallet for i in range(N_total): res = self.wallet[i].is_multisig() assert res.multisig == True @@ -159,6 +229,73 @@ class MultisigTest(): assert res.threshold == M_threshold assert res.total == N_total + # We want to test if multisig wallets can receive normal transfers as well and mining transfers + def fund_addrs_with_normal_wallet(self, addrs): + print("Funding multisig wallets with normal wallet-to-wallet transfers") + + # Generate normal deterministic wallet + normal_seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted' + assert not hasattr(self, 'wallet') or not self.wallet + self.wallet = [Wallet(idx = 0)] + res = self.wallet[0].restore_deterministic_wallet(seed = normal_seed) + assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + + self.wallet[0].refresh() + + # Check that we own enough spendable enotes + res = self.wallet[0].incoming_transfers(transfer_type = 'available') + assert 'transfers' in res + num_outs_spendable = 0 + min_out_amount = None + for t in res.transfers: + if not t.spent: + num_outs_spendable += 1 + min_out_amount = min(min_out_amount, t.amount) if min_out_amount is not None else t.amount + assert num_outs_spendable >= 2 * len(addrs) + + # Transfer to addrs and mine to confirm tx + dsts = [{'address': addr, 'amount': int(min_out_amount * 0.95)} for addr in addrs] + res = self.wallet[0].transfer(dsts, get_tx_metadata = True) + tx_hex = res.tx_metadata + res = self.wallet[0].relay_tx(tx_hex) + self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 10) + + def remake_some_multisig_wallets_by_multsig_seed(self, threshold): + N = len(self.wallet) + signers_to_remake = set() + num_signers_to_remake = random.randint(1, N) # Do at least one + while len(signers_to_remake) < num_signers_to_remake: + signers_to_remake.add(random.randint(0, N - 1)) + + for i in signers_to_remake: + print("Remaking {}/{} multsig wallet from multisig seed: #{}".format(threshold, N, i+1)) + + otherwise_unused_seed = \ + 'factual wiggle awakened maul sash biscuit pause reinvest fonts sleepless knowledge tossed jewels request gusts dagger gumball onward dotted amended powder cynical strained topic request' + + # Get information about wallet, will compare against later + old_viewkey = self.wallet[i].query_key('view_key').key + old_spendkey = self.wallet[i].query_key('spend_key').key + old_multisig_seed = self.wallet[i].query_key('mnemonic').key + + # Close old wallet and restore w/ random seed so we know that restoring actually did something + self.wallet[i].close_wallet() + self.wallet[i].restore_deterministic_wallet(seed=otherwise_unused_seed) + mid_viewkey = self.wallet[i].query_key('view_key').key + assert mid_viewkey != old_viewkey + + # Now restore w/ old multisig seed and check against original + self.wallet[i].close_wallet() + self.wallet[i].restore_deterministic_wallet(seed=old_multisig_seed, enable_multisig_experimental=True) + new_viewkey = self.wallet[i].query_key('view_key').key + new_spendkey = self.wallet[i].query_key('spend_key').key + new_multisig_seed = self.wallet[i].query_key('mnemonic').key + assert new_viewkey == old_viewkey + assert new_spendkey == old_spendkey + assert new_multisig_seed == old_multisig_seed + + self.wallet[i].refresh() + def test_states(self): print('Testing multisig states') seeds = [ @@ -251,7 +388,7 @@ class MultisigTest(): assert res.n_outputs == expected_outputs def transfer(self, signers): - assert len(signers) >= 2 + assert len(signers) >= 1 daemon = Daemon() diff --git a/tests/unit_tests/long_term_block_weight.cpp b/tests/unit_tests/long_term_block_weight.cpp index b9ce2b247..e52bd1955 100644 --- a/tests/unit_tests/long_term_block_weight.cpp +++ b/tests/unit_tests/long_term_block_weight.cpp @@ -106,16 +106,9 @@ static uint32_t lcg() } -struct BlockchainAndPool -{ - cryptonote::tx_memory_pool txpool; - cryptonote::Blockchain bc; - BlockchainAndPool(): txpool(bc), bc(txpool) {} -}; - #define PREFIX_WINDOW(hf_version,window) \ - BlockchainAndPool bap; \ - cryptonote::Blockchain *bc = &bap.bc; \ + cryptonote::BlockchainAndPool bap; \ + cryptonote::Blockchain *bc = &bap.blockchain; \ struct get_test_options { \ const std::pair<uint8_t, uint64_t> hard_forks[3]; \ const cryptonote::test_options test_options = { \ diff --git a/tests/unit_tests/net.cpp b/tests/unit_tests/net.cpp index 03072b283..b9555b213 100644 --- a/tests/unit_tests/net.cpp +++ b/tests/unit_tests/net.cpp @@ -74,6 +74,8 @@ namespace "xmrto2bturnore26.onion"; static constexpr const char v3_onion[] = "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion"; + static constexpr const char v3_onion_2[] = + "zpv4fa3szgel7vf6jdjeugizdclq2vzkelscs2bhbgnlldzzggcen3ad.onion"; } TEST(tor_address, constants) @@ -94,12 +96,10 @@ TEST(tor_address, invalid) EXPECT_TRUE(net::tor_address::make(":").has_error()); EXPECT_TRUE(net::tor_address::make(".onion").has_error()); EXPECT_TRUE(net::tor_address::make(".onion:").has_error()); - EXPECT_TRUE(net::tor_address::make(v2_onion + 1).has_error()); EXPECT_TRUE(net::tor_address::make(v3_onion + 1).has_error()); - EXPECT_TRUE(net::tor_address::make(boost::string_ref{v2_onion, sizeof(v2_onion) - 2}).has_error()); EXPECT_TRUE(net::tor_address::make(boost::string_ref{v3_onion, sizeof(v3_onion) - 2}).has_error()); - EXPECT_TRUE(net::tor_address::make(std::string{v2_onion} + ":-").has_error()); - EXPECT_TRUE(net::tor_address::make(std::string{v2_onion} + ":900a").has_error()); + EXPECT_TRUE(net::tor_address::make(std::string{v3_onion} + ":-").has_error()); + EXPECT_TRUE(net::tor_address::make(std::string{v3_onion} + ":900a").has_error()); EXPECT_TRUE(net::tor_address::make(std::string{v3_onion} + ":65536").has_error()); EXPECT_TRUE(net::tor_address::make(std::string{v3_onion} + ":-1").has_error()); @@ -163,11 +163,11 @@ TEST(tor_address, valid) EXPECT_FALSE(address2.less(*address1)); EXPECT_FALSE(address1->less(address2)); - address2 = MONERO_UNWRAP(net::tor_address::make(std::string{v2_onion} + ":6545")); + address2 = MONERO_UNWRAP(net::tor_address::make(std::string{v3_onion_2} + ":6545")); EXPECT_EQ(6545, address2.port()); - EXPECT_STREQ(v2_onion, address2.host_str()); - EXPECT_EQ(std::string{v2_onion} + ":6545", address2.str().c_str()); + EXPECT_STREQ(v3_onion_2, address2.host_str()); + EXPECT_EQ(std::string{v3_onion_2} + ":6545", address2.str().c_str()); EXPECT_TRUE(address2.is_blockable()); EXPECT_FALSE(address2.equal(*address1)); EXPECT_FALSE(address1->equal(address2)); @@ -244,57 +244,6 @@ namespace }; } -TEST(tor_address, epee_serializev_v2) -{ - epee::byte_slice buffer{}; - { - test_command_tor command{MONERO_UNWRAP(net::tor_address::make(v2_onion, 10))}; - EXPECT_FALSE(command.tor.is_unknown()); - EXPECT_NE(net::tor_address{}, command.tor); - EXPECT_STREQ(v2_onion, command.tor.host_str()); - EXPECT_EQ(10u, command.tor.port()); - - epee::serialization::portable_storage stg{}; - EXPECT_TRUE(command.store(stg)); - EXPECT_TRUE(stg.store_to_binary(buffer)); - } - - test_command_tor command{}; - { - EXPECT_TRUE(command.tor.is_unknown()); - EXPECT_EQ(net::tor_address{}, command.tor); - EXPECT_STREQ(net::tor_address::unknown_str(), command.tor.host_str()); - EXPECT_EQ(0u, command.tor.port()); - - epee::serialization::portable_storage stg{}; - EXPECT_TRUE(stg.load_from_binary(epee::to_span(buffer))); - EXPECT_TRUE(command.load(stg)); - } - EXPECT_FALSE(command.tor.is_unknown()); - EXPECT_NE(net::tor_address{}, command.tor); - EXPECT_STREQ(v2_onion, command.tor.host_str()); - EXPECT_EQ(10u, command.tor.port()); - - // make sure that exceeding max buffer doesn't destroy tor_address::_load - { - epee::serialization::portable_storage stg{}; - stg.load_from_binary(epee::to_span(buffer)); - - std::string host{}; - ASSERT_TRUE(stg.get_value("host", host, stg.open_section("tor", nullptr, false))); - EXPECT_EQ(std::strlen(v2_onion), host.size()); - - host.push_back('k'); - EXPECT_TRUE(stg.set_value("host", std::move(host), stg.open_section("tor", nullptr, false))); - EXPECT_TRUE(command.load(stg)); // poor error reporting from `KV_SERIALIZE` - } - - EXPECT_TRUE(command.tor.is_unknown()); - EXPECT_EQ(net::tor_address{}, command.tor); - EXPECT_STREQ(net::tor_address::unknown_str(), command.tor.host_str()); - EXPECT_EQ(0u, command.tor.port()); -} - TEST(tor_address, epee_serializev_v3) { epee::byte_slice buffer{}; @@ -397,41 +346,6 @@ TEST(tor_address, epee_serialize_unknown) EXPECT_EQ(0u, command.tor.port()); } -TEST(tor_address, boost_serialize_v2) -{ - std::string buffer{}; - { - const net::tor_address tor = MONERO_UNWRAP(net::tor_address::make(v2_onion, 10)); - EXPECT_FALSE(tor.is_unknown()); - EXPECT_NE(net::tor_address{}, tor); - EXPECT_STREQ(v2_onion, tor.host_str()); - EXPECT_EQ(10u, tor.port()); - - std::ostringstream stream{}; - { - boost::archive::portable_binary_oarchive archive{stream}; - archive << tor; - } - buffer = stream.str(); - } - - net::tor_address tor{}; - { - EXPECT_TRUE(tor.is_unknown()); - EXPECT_EQ(net::tor_address{}, tor); - EXPECT_STREQ(net::tor_address::unknown_str(), tor.host_str()); - EXPECT_EQ(0u, tor.port()); - - std::istringstream stream{buffer}; - boost::archive::portable_binary_iarchive archive{stream}; - archive >> tor; - } - EXPECT_FALSE(tor.is_unknown()); - EXPECT_NE(net::tor_address{}, tor); - EXPECT_STREQ(v2_onion, tor.host_str()); - EXPECT_EQ(10u, tor.port()); -} - TEST(tor_address, boost_serialize_v3) { std::string buffer{}; @@ -511,6 +425,9 @@ TEST(get_network_address, onion) address = net::get_network_address(".onion", 0); EXPECT_EQ(net::error::invalid_tor_address, address); + address = net::get_network_address(v2_onion, 1000); + EXPECT_EQ(net::error::invalid_tor_address, address); + address = net::get_network_address(v3_onion, 1000); ASSERT_TRUE(bool(address)); EXPECT_EQ(epee::net_utils::address_type::tor, address->get_type_id()); diff --git a/tests/unit_tests/output_distribution.cpp b/tests/unit_tests/output_distribution.cpp index ab474a7d8..038c19874 100644 --- a/tests/unit_tests/output_distribution.cpp +++ b/tests/unit_tests/output_distribution.cpp @@ -30,10 +30,7 @@ #include "gtest/gtest.h" #include "misc_log_ex.h" #include "rpc/rpc_handler.h" -#include "blockchain_db/blockchain_db.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/tx_pool.h" -#include "cryptonote_core/blockchain.h" #include "blockchain_db/testdb.h" static const uint64_t test_distribution[32] = { @@ -77,9 +74,6 @@ public: bool get_output_distribution(uint64_t amount, uint64_t from, uint64_t to, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) { - std::unique_ptr<cryptonote::Blockchain> bc; - cryptonote::tx_memory_pool txpool(*bc); - bc.reset(new cryptonote::Blockchain(txpool)); struct get_test_options { const std::pair<uint8_t, uint64_t> hard_forks[2]; const cryptonote::test_options test_options = { @@ -87,9 +81,9 @@ bool get_output_distribution(uint64_t amount, uint64_t from, uint64_t to, uint64 }; get_test_options():hard_forks{std::make_pair((uint8_t)1, (uint64_t)0), std::make_pair((uint8_t)0, (uint64_t)0)}{} } opts; - cryptonote::Blockchain *blockchain = bc.get(); - bool r = blockchain->init(new TestDB(test_distribution_size), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); - return r && bc->get_output_distribution(amount, from, to, start_height, distribution, base); + cryptonote::BlockchainAndPool bap; + bool r = bap.blockchain.init(new TestDB(test_distribution_size), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); + return r && bap.blockchain.get_output_distribution(amount, from, to, start_height, distribution, base); } crypto::hash get_block_hash(uint64_t height) diff --git a/tests/unit_tests/scaling_2021.cpp b/tests/unit_tests/scaling_2021.cpp index 024a4b4fd..d90f0f9e6 100644 --- a/tests/unit_tests/scaling_2021.cpp +++ b/tests/unit_tests/scaling_2021.cpp @@ -50,9 +50,6 @@ public: } #define PREFIX_WINDOW(hf_version,window) \ - std::unique_ptr<cryptonote::Blockchain> bc; \ - cryptonote::tx_memory_pool txpool(*bc); \ - bc.reset(new cryptonote::Blockchain(txpool)); \ struct get_test_options { \ const std::pair<uint8_t, uint64_t> hard_forks[3]; \ const cryptonote::test_options test_options = { \ @@ -61,7 +58,9 @@ public: }; \ get_test_options(): hard_forks{std::make_pair(1, (uint64_t)0), std::make_pair((uint8_t)hf_version, (uint64_t)1), std::make_pair((uint8_t)0, (uint64_t)0)} {} \ } opts; \ - cryptonote::Blockchain *blockchain = bc.get(); \ + cryptonote::BlockchainAndPool bap; \ + cryptonote::Blockchain *blockchain = &bap.blockchain; \ + cryptonote::Blockchain *bc = blockchain; \ bool r = blockchain->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \ ASSERT_TRUE(r) diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index d78cb732f..1c42f7f04 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -297,7 +297,7 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(query_key) - def restore_deterministic_wallet(self, seed = '', seed_offset = '', filename = '', restore_height = 0, password = '', language = '', autosave_current = True): + def restore_deterministic_wallet(self, seed = '', seed_offset = '', filename = '', restore_height = 0, password = '', language = '', autosave_current = True, enable_multisig_experimental = False): restore_deterministic_wallet = { 'method': 'restore_deterministic_wallet', 'params' : { @@ -308,6 +308,7 @@ class Wallet(object): 'password': password, 'language': language, 'autosave_current': autosave_current, + 'enable_multisig_experimental': enable_multisig_experimental }, 'jsonrpc': '2.0', 'id': '0' |