aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt21
-rw-r--r--Makefile4
-rw-r--r--cmake/FindLibunwind.cmake16
-rw-r--r--cmake/test-static-assert.cpp34
-rw-r--r--contrib/epee/include/console_handler.h2
-rw-r--r--contrib/epee/include/net/http_auth.h118
-rw-r--r--contrib/epee/include/net/http_protocol_handler.h6
-rw-r--r--contrib/epee/include/net/http_server_impl_base.h2
-rw-r--r--contrib/epee/src/http_auth.cpp503
-rw-r--r--contrib/otshell_utils/CMakeLists.txt4
-rw-r--r--external/boost/archive/portable_binary_archive.hpp5
-rw-r--r--external/boost/archive/portable_binary_iarchive.hpp15
-rw-r--r--external/boost/archive/portable_binary_oarchive.hpp18
-rw-r--r--external/db_drivers/liblmdb/CMakeLists.txt5
-rw-r--r--external/unbound/compat/getentropy_linux.c6
-rw-r--r--src/blockchain_db/blockchain_db.h14
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp14
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h2
-rw-r--r--src/common/dns_utils.cpp143
-rw-r--r--src/common/dns_utils.h19
-rw-r--r--src/common/int-util.h14
-rw-r--r--src/crypto/CMakeLists.txt11
-rw-r--r--src/crypto/oaes_lib.c6
-rw-r--r--src/crypto/slow-hash.c11
-rw-r--r--src/crypto/tree-hash.c38
-rw-r--r--src/cryptonote_core/blockchain.cpp15
-rw-r--r--src/cryptonote_core/cryptonote_format_utils.h33
-rw-r--r--src/cryptonote_core/tx_pool.cpp47
-rw-r--r--src/cryptonote_core/tx_pool.h3
-rw-r--r--src/daemon/command_parser_executor.cpp32
-rw-r--r--src/daemon/rpc_command_executor.cpp3
-rw-r--r--src/rpc/core_rpc_server.cpp5
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h16
-rw-r--r--src/simplewallet/simplewallet.cpp567
-rw-r--r--src/simplewallet/simplewallet.h8
-rw-r--r--src/wallet/api/address_book.cpp22
-rw-r--r--src/wallet/api/address_book.h2
-rw-r--r--src/wallet/api/wallet.cpp76
-rw-r--r--src/wallet/api/wallet.h7
-rw-r--r--src/wallet/api/wallet_manager.cpp74
-rw-r--r--src/wallet/api/wallet_manager.h7
-rw-r--r--src/wallet/wallet2.cpp162
-rw-r--r--src/wallet/wallet2.h93
-rw-r--r--src/wallet/wallet2_api.h39
-rw-r--r--src/wallet/wallet_rpc_server.cpp215
-rw-r--r--src/wallet/wallet_rpc_server.h8
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h73
-rw-r--r--tests/CMakeLists.txt8
-rw-r--r--tests/data/outputsbin0 -> 1035 bytes
-rw-r--r--tests/data/signed_monero_txbin0 -> 15284 bytes
-rw-r--r--tests/data/unsigned_monero_txbin0 -> 1753 bytes
-rw-r--r--tests/data/wallet_9svHk1bin0 -> 1971 bytes
-rw-r--r--tests/data/wallet_9svHk1.keysbin0 -> 759 bytes
-rw-r--r--tests/unit_tests/address_from_url.cpp17
-rw-r--r--tests/unit_tests/hardfork.cpp1
-rw-r--r--tests/unit_tests/http_auth.cpp351
-rw-r--r--tests/unit_tests/serialization.cpp513
57 files changed, 2820 insertions, 608 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 87a53c368..e42cdb384 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -174,6 +174,9 @@ include_directories(src contrib/epee/include external "${CMAKE_BINARY_DIR}/versi
if(APPLE)
include_directories(SYSTEM /usr/include/malloc)
+ if(POLICY CMP0042)
+ cmake_policy(SET CMP0042 NEW)
+ endif()
endif()
if(MSVC OR MINGW)
@@ -375,6 +378,13 @@ else()
set(STATIC_ASSERT_FLAG "-Dstatic_assert=_Static_assert")
endif()
+ try_compile(STATIC_ASSERT_CPP_RES "${CMAKE_CURRENT_BINARY_DIR}/static-assert" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/test-static-assert.cpp" COMPILE_DEFINITIONS "-std=c++11")
+ if(STATIC_ASSERT_CPP_RES)
+ set(STATIC_ASSERT_CPP_FLAG "")
+ else()
+ set(STATIC_ASSERT_CPP_FLAG "-Dstatic_assert=_Static_assert")
+ endif()
+
option(COVERAGE "Enable profiling for test coverage report" 0)
if(COVERAGE)
@@ -410,7 +420,7 @@ else()
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -D_GNU_SOURCE ${MINGW_FLAG} ${STATIC_ASSERT_FLAG} ${WARNINGS} ${C_WARNINGS} ${ARCH_FLAG} ${COVERAGE_FLAGS} ${PIC_FLAG}")
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -D_GNU_SOURCE ${MINGW_FLAG} ${STATIC_ASSERT_FLAG} ${WARNINGS} ${CXX_WARNINGS} ${ARCH_FLAG} ${COVERAGE_FLAGS} ${PIC_FLAG}")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -D_GNU_SOURCE ${MINGW_FLAG} ${STATIC_ASSERT_CPP_FLAG} ${WARNINGS} ${CXX_WARNINGS} ${ARCH_FLAG} ${COVERAGE_FLAGS} ${PIC_FLAG}")
# With GCC 6.1.1 the compiled binary malfunctions due to aliasing. Until that
# is fixed in the code (Issue #847), force compiler to be conservative.
@@ -494,6 +504,13 @@ else()
endif(ARM)
+ if(ANDROID AND NOT BUILD_GUI_DEPS STREQUAL "ON")
+ #From Android 5: "only position independent executables (PIE) are supported"
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIE")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_CXX_FLAGS} -fPIE -pie")
+ endif()
+
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGTEST_HAS_TR1_TUPLE=0")
endif()
@@ -580,7 +597,7 @@ endif()
include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
if(MINGW)
set(EXTRA_LIBRARIES mswsock;ws2_32;iphlpapi)
-elseif(APPLE OR FREEBSD OR OPENBSD)
+elseif(APPLE OR FREEBSD OR OPENBSD OR ANDROID)
set(EXTRA_LIBRARIES "")
elseif(DRAGONFLY)
find_library(COMPAT compat)
diff --git a/Makefile b/Makefile
index 777769f13..e6517ebac 100644
--- a/Makefile
+++ b/Makefile
@@ -70,6 +70,10 @@ release-static-armv7:
mkdir -p build/release
cd build/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release ../.. && $(MAKE)
+release-static-android:
+ mkdir -p build/release
+ cd build/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON ../.. && $(MAKE)
+
release-static-armv8:
mkdir -p build/release
cd build/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv8-a" -D STATIC=ON -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release ../.. && $(MAKE)
diff --git a/cmake/FindLibunwind.cmake b/cmake/FindLibunwind.cmake
index 9946e7cdd..af657cad1 100644
--- a/cmake/FindLibunwind.cmake
+++ b/cmake/FindLibunwind.cmake
@@ -17,6 +17,22 @@ find_path(LIBUNWIND_INCLUDE_DIR libunwind.h
)
find_library(LIBUNWIND_LIBRARIES NAMES unwind )
+if(NOT LIBUNWIND_LIBRARIES STREQUAL "LIBUNWIND_LIBRARIES-NOTFOUND")
+ if (CMAKE_COMPILER_IS_GNUCC)
+ set(LIBUNWIND_LIBRARIES "gcc_eh;${LIBUNWIND_LIBRARIES}")
+ endif()
+endif()
+
+# some versions of libunwind need liblzma, and we don't use pkg-config
+# so we just look whether liblzma is installed, and add it if it is.
+# It might not be actually needed, but doesn't hurt if it is not.
+# We don't need any headers, just the lib, as it's privately needed.
+message(STATUS "looking for liblzma")
+find_library(LIBLZMA_LIBRARIES lzma )
+if(NOT LIBLZMA_LIBRARIES STREQUAL "LIBLZMA_LIBRARIES-NOTFOUND")
+ message(STATUS "liblzma found")
+ set(LIBUNWIND_LIBRARIES "${LIBUNWIND_LIBRARIES};${LIBLZMA_LIBRARIES}")
+endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libunwind "Could not find libunwind" LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARIES)
diff --git a/cmake/test-static-assert.cpp b/cmake/test-static-assert.cpp
new file mode 100644
index 000000000..0c6dfb151
--- /dev/null
+++ b/cmake/test-static-assert.cpp
@@ -0,0 +1,34 @@
+// Copyright (c) 2014-2016, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <assert.h>
+
+static_assert(1, "FAIL");
+int main(int argc, char *argv[]) {
+ return 0;
+}
diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h
index 2ad92b3f2..554a48488 100644
--- a/contrib/epee/include/console_handler.h
+++ b/contrib/epee/include/console_handler.h
@@ -133,7 +133,7 @@ namespace epee
bool wait_stdin_data()
{
#if !defined(WIN32)
- #ifdef __OpenBSD__
+ #if defined(__OpenBSD__) || defined(__ANDROID__)
int stdin_fileno = fileno(stdin);
#else
int stdin_fileno = ::fileno(stdin);
diff --git a/contrib/epee/include/net/http_auth.h b/contrib/epee/include/net/http_auth.h
index 1931b6115..bdbfa7524 100644
--- a/contrib/epee/include/net/http_auth.h
+++ b/contrib/epee/include/net/http_auth.h
@@ -28,51 +28,137 @@
#pragma once
#include <boost/optional/optional.hpp>
+#include <boost/utility/string_ref.hpp>
#include <cstdint>
-#include "http_base.h"
+#include <functional>
#include <string>
#include <utility>
+#include "http_base.h"
+
namespace epee
{
namespace net_utils
{
namespace http
{
+ struct login
+ {
+ login() : username(), password() {}
+ login(std::string username_, std::string password_)
+ : username(std::move(username_)), password(std::move(password_))
+ {}
+
+ std::string username;
+ std::string password;
+ };
+
//! Implements RFC 2617 digest auth. Digests from RFC 7616 can be added.
- class http_auth
+ class http_server_auth
{
public:
- struct login
- {
- login() = delete;
- std::string username;
- std::string password;
- };
-
struct session
{
- session() = delete;
- const login credentials;
+ session(login credentials_)
+ : credentials(std::move(credentials_)), nonce(), counter(0)
+ {}
+
+ login credentials;
std::string nonce;
std::uint32_t counter;
};
- http_auth() : user() {}
- http_auth(login credentials);
+ http_server_auth() : user() {}
+ http_server_auth(login credentials);
//! \return Auth response, or `boost::none` iff `request` had valid auth.
boost::optional<http_response_info> get_response(const http_request_info& request)
{
if (user)
+ return do_get_response(request);
+ return boost::none;
+ }
+ private:
+ boost::optional<http_response_info> do_get_response(const http_request_info& request);
+
+ boost::optional<session> user;
+ };
+
+ //! Implements RFC 2617 digest auth. Digests from RFC 7616 can be added.
+ class http_client_auth
+ {
+ public:
+ enum status : std::uint8_t { kSuccess = 0, kBadPassword, kParseFailure };
+
+ struct session
+ {
+ session(login credentials_)
+ : credentials(std::move(credentials_)), server(), counter(0)
+ {}
+
+ struct keys
{
- return process(request);
- }
+ using algorithm =
+ std::function<std::string(const session&, boost::string_ref, boost::string_ref)>;
+
+ keys() : nonce(), opaque(), realm(), generator() {}
+ keys(std::string nonce_, std::string opaque_, std::string realm_, algorithm generator_)
+ : nonce(std::move(nonce_))
+ , opaque(std::move(opaque_))
+ , realm(std::move(realm_))
+ , generator(std::move(generator_))
+ {}
+
+ std::string nonce;
+ std::string opaque;
+ std::string realm;
+ algorithm generator;
+ };
+
+ login credentials;
+ keys server;
+ std::uint32_t counter;
+ };
+
+ http_client_auth() : user() {}
+ http_client_auth(login credentials);
+
+ /*!
+ Clients receiving a 401 response code from the server should call this
+ function to process the server auth. Then, before every client request,
+ `get_auth_field()` should be called to retrieve the newest
+ authorization request.
+
+ \return `kBadPassword` if client will never be able to authenticate,
+ `kParseFailure` if all server authentication responses were invalid,
+ and `kSuccess` if `get_auth_field` is ready to generate authorization
+ fields.
+ */
+ status handle_401(const http_response_info& response)
+ {
+ if (user)
+ return do_handle_401(response);
+ return kBadPassword;
+ }
+
+ /*!
+ After calling `handle_401`, clients should call this function to
+ generate an authentication field for every request.
+
+ \return A HTTP "Authorization" field if `handle_401(...)` previously
+ returned `kSuccess`.
+ */
+ boost::optional<std::pair<std::string, std::string>> get_auth_field(
+ const boost::string_ref method, const boost::string_ref uri)
+ {
+ if (user)
+ return do_get_auth_field(method, uri);
return boost::none;
}
private:
- boost::optional<http_response_info> process(const http_request_info& request);
+ status do_handle_401(const http_response_info&);
+ boost::optional<std::pair<std::string, std::string>> do_get_auth_field(boost::string_ref, boost::string_ref);
boost::optional<session> user;
};
diff --git a/contrib/epee/include/net/http_protocol_handler.h b/contrib/epee/include/net/http_protocol_handler.h
index 3813f9d7c..69ea04fbe 100644
--- a/contrib/epee/include/net/http_protocol_handler.h
+++ b/contrib/epee/include/net/http_protocol_handler.h
@@ -52,7 +52,7 @@ namespace net_utils
{
std::string m_folder;
std::string m_required_user_agent;
- boost::optional<http_auth::login> m_user;
+ boost::optional<login> m_user;
critical_section m_lock;
};
@@ -173,7 +173,7 @@ namespace net_utils
: simple_http_connection_handler<t_connection_context>(psnd_hndlr, config),
m_config(config),
m_conn_context(conn_context),
- m_auth(m_config.m_user ? http_auth{*m_config.m_user} : http_auth{})
+ m_auth(m_config.m_user ? http_server_auth{*m_config.m_user} : http_server_auth{})
{}
inline bool handle_request(const http_request_info& query_info, http_response_info& response)
{
@@ -214,7 +214,7 @@ namespace net_utils
//simple_http_connection_handler::config_type m_stub_config;
config_type& m_config;
t_connection_context& m_conn_context;
- http_auth m_auth;
+ http_server_auth m_auth;
};
}
}
diff --git a/contrib/epee/include/net/http_server_impl_base.h b/contrib/epee/include/net/http_server_impl_base.h
index f6b2d6941..a5cc1a917 100644
--- a/contrib/epee/include/net/http_server_impl_base.h
+++ b/contrib/epee/include/net/http_server_impl_base.h
@@ -53,7 +53,7 @@ namespace epee
{}
bool init(const std::string& bind_port = "0", const std::string& bind_ip = "0.0.0.0",
- std::string user_agent = "", boost::optional<net_utils::http::http_auth::login> user = boost::none)
+ std::string user_agent = "", boost::optional<net_utils::http::login> user = boost::none)
{
//set self as callback handler
diff --git a/contrib/epee/src/http_auth.cpp b/contrib/epee/src/http_auth.cpp
index 98870a7a9..7fd32dabc 100644
--- a/contrib/epee/src/http_auth.cpp
+++ b/contrib/epee/src/http_auth.cpp
@@ -28,34 +28,41 @@
#include "net/http_auth.h"
#include <array>
+#include <boost/algorithm/string/find_iterator.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
+#include <boost/fusion/algorithm/iteration/iter_fold.hpp>
#include <boost/fusion/algorithm/query/any.hpp>
+#include <boost/fusion/iterator/distance.hpp>
+#include <boost/fusion/iterator/value_of.hpp>
+#include <boost/fusion/sequence/intrinsic/begin.hpp>
+#include <boost/fusion/sequence/intrinsic/size.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/range/iterator_range_core.hpp>
#include <boost/range/join.hpp>
+#include <boost/spirit/include/karma_generate.hpp>
+#include <boost/spirit/include/karma_uint.hpp>
#include <boost/spirit/include/qi_alternative.hpp>
#include <boost/spirit/include/qi_and_predicate.hpp>
#include <boost/spirit/include/qi_char.hpp>
#include <boost/spirit/include/qi_char_class.hpp>
#include <boost/spirit/include/qi_difference.hpp>
#include <boost/spirit/include/qi_kleene.hpp>
-#include <boost/spirit/include/qi_optional.hpp>
#include <boost/spirit/include/qi_parse.hpp>
#include <boost/spirit/include/qi_plus.hpp>
#include <boost/spirit/include/qi_no_case.hpp>
+#include <boost/spirit/include/qi_not_predicate.hpp>
#include <boost/spirit/include/qi_raw.hpp>
#include <boost/spirit/include/qi_rule.hpp>
#include <boost/spirit/include/qi_sequence.hpp>
#include <boost/spirit/include/qi_string.hpp>
#include <boost/spirit/include/qi_symbols.hpp>
#include <boost/spirit/include/qi_uint.hpp>
-#include <boost/utility/string_ref.hpp>
#include <cassert>
-#include <functional>
#include <iterator>
+#include <limits>
#include <tuple>
#include <type_traits>
@@ -75,6 +82,8 @@ characters without "consuming" the input character. */
namespace
{
+ namespace http = epee::net_utils::http;
+
// string_ref is only constexpr if length is given
template<std::size_t N>
constexpr boost::string_ref ceref(const char (&arg)[N])
@@ -82,12 +91,17 @@ namespace
return boost::string_ref(arg, N - 1);
}
- constexpr const auto auth_realm = ceref(u8"monero-wallet-rpc");
+ constexpr const auto client_auth_field = ceref(u8"Authorization");
+ constexpr const auto server_auth_field = ceref(u8"WWW-authenticate");
+ constexpr const auto auth_realm = ceref(u8"monero-rpc");
constexpr const char comma = 44;
constexpr const char equal_sign = 61;
constexpr const char quote = 34;
+ constexpr const char zero = 48;
constexpr const auto sess_algo = ceref(u8"-sess");
+ constexpr const unsigned client_reserve_size = 512; //!< std::string::reserve size for clients
+
//// Digest Algorithms
template<std::size_t N>
@@ -147,15 +161,15 @@ namespace
};
constexpr const boost::string_ref md5_::name;
- //! Digest Algorithms available for HTTP Digest Auth.
+ //! Digest Algorithms available for HTTP Digest Auth. Sort better algos to the left
constexpr const std::tuple<md5_> digest_algorithms{};
- //// Various String Parsing Utilities
+ //// Various String Utilities
struct ascii_tolower_
{
template<typename Char>
- Char operator()(Char value) const noexcept
+ constexpr Char operator()(Char value) const noexcept
{
static_assert(std::is_integral<Char>::value, "only integral types supported");
return (65 <= value && value <= 90) ? (value + 32) : value;
@@ -166,34 +180,243 @@ namespace
struct ascii_iequal_
{
template<typename Char>
- bool operator()(Char left, Char right) const noexcept
+ constexpr bool operator()(Char left, Char right) const noexcept
{
return ascii_tolower(left) == ascii_tolower(right);
}
};
constexpr const ascii_iequal_ ascii_iequal{};
+ struct http_list_separator_
+ {
+ template<typename Char>
+ bool operator()(Char value) const noexcept
+ {
+ static_assert(std::is_integral<Char>::value, "only integral types supported");
+ return boost::spirit::char_encoding::ascii::isascii_(value) &&
+ (value == comma || boost::spirit::char_encoding::ascii::isspace(value));
+ }
+ };
+ constexpr const http_list_separator_ http_list_separator{};
+
+ std::string to_string(boost::iterator_range<const char*> source)
+ {
+ return {source.begin(), source.size()};
+ }
+
+ template<typename T>
+ void add_first_field(std::string& str, const char* const name, const T& value)
+ {
+ str.append(name);
+ str.push_back(equal_sign);
+ boost::copy(value, std::back_inserter(str));
+ }
+
+ template<typename T>
+ void add_field(std::string& str, const char* const name, const T& value)
+ {
+ str.push_back(comma);
+ add_first_field(str, name, value);
+ }
+
+ template<typename T>
+ using quoted_result = boost::joined_range<
+ const boost::joined_range<const boost::string_ref, const T>, const boost::string_ref
+ >;
+
+ template<typename T>
+ quoted_result<T> quoted(const T& arg)
+ {
+ return boost::range::join(boost::range::join(ceref(u8"\""), arg), ceref(u8"\""));
+ }
+
//// Digest Authentication
-
- struct auth_request
+
+ template<typename Digest>
+ typename std::result_of<Digest()>::type generate_a1(
+ Digest digest, const http::login& creds, const boost::string_ref realm)
+ {
+ return digest(creds.username, u8":", realm, u8":", creds.password);
+ }
+
+ template<typename Digest>
+ typename std::result_of<Digest()>::type generate_a1(
+ Digest digest, const http::http_client_auth::session& user)
+ {
+ return generate_a1(std::move(digest), user.credentials, user.server.realm);
+ }
+
+ template<typename T>
+ void init_client_value(std::string& str,
+ const boost::string_ref algorithm, const http::http_client_auth::session& user,
+ const boost::string_ref uri, const T& response)
+ {
+ str.append(u8"Digest ");
+ add_first_field(str, u8"algorithm", algorithm);
+ add_field(str, u8"nonce", quoted(user.server.nonce));
+ add_field(str, u8"realm", quoted(user.server.realm));
+ add_field(str, u8"response", quoted(response));
+ add_field(str, u8"uri", quoted(uri));
+ add_field(str, u8"username", quoted(user.credentials.username));
+ if (!user.server.opaque.empty())
+ add_field(str, u8"opaque", quoted(user.server.opaque));
+ }
+
+ //! Implements superseded algorithm specified in RFC 2069
+ template<typename Digest>
+ struct old_algorithm
+ {
+ explicit old_algorithm(Digest digest_) : digest(std::move(digest_)) {}
+
+ std::string operator()(const http::http_client_auth::session& user,
+ const boost::string_ref method, const boost::string_ref uri) const
+ {
+ const auto response = digest(
+ generate_a1(digest, user), u8":", user.server.nonce, u8":", digest(method, u8":", uri)
+ );
+ std::string out{};
+ out.reserve(client_reserve_size);
+ init_client_value(out, Digest::name, user, uri, response);
+ return out;
+ }
+ private:
+ Digest digest;
+ };
+
+ //! Implements the `qop=auth` algorithm specified in RFC 2617
+ template<typename Digest>
+ struct auth_algorithm
+ {
+ explicit auth_algorithm(Digest digest_) : digest(std::move(digest_)) {}
+
+ std::string operator()(const http::http_client_auth::session& user,
+ const boost::string_ref method, const boost::string_ref uri) const
+ {
+ namespace karma = boost::spirit::karma;
+ using counter_type = decltype(user.counter);
+ static_assert(
+ std::numeric_limits<counter_type>::radix == 2, "unexpected radix for counter"
+ );
+ static_assert(
+ std::numeric_limits<counter_type>::digits <= 32,
+ "number of digits will cause underflow on padding below"
+ );
+
+ std::string out{};
+ out.reserve(client_reserve_size);
+
+ karma::generate(std::back_inserter(out), karma::hex(user.counter));
+ out.insert(out.begin(), 8 - out.size(), zero); // zero left pad
+ if (out.size() != 8)
+ return {};
+
+ std::array<char, 8> nc{{}};
+ boost::copy(out, nc.data());
+ const auto response = digest(
+ generate_a1(digest, user), u8":", user.server.nonce, u8":", nc, u8"::auth:", digest(method, u8":", uri)
+ );
+ out.clear();
+ init_client_value(out, Digest::name, user, uri, response);
+ add_field(out, u8"qop", ceref(u8"auth"));
+ add_field(out, u8"nc", nc);
+ return out;
+ }
+
+ private:
+ Digest digest;
+ };
+
+ //! Processes client "Authorization" and server "WWW-authenticate" HTTP fields
+ struct auth_message
{
using iterator = const char*;
enum status{ kFail = 0, kStale, kPass };
- static status verify(const std::string& method, const std::string& request,
- const epee::net_utils::http::http_auth::session& user)
+ //! \return Status of the `response` field from the client
+ static status verify(const boost::string_ref method, const boost::string_ref request,
+ const http::http_server_auth::session& user)
+ {
+ const auto parsed = parse(request);
+ if (parsed &&
+ boost::equals(parsed->username, user.credentials.username) &&
+ boost::fusion::any(digest_algorithms, has_valid_response{*parsed, user, method}))
+ {
+ if (boost::equals(parsed->nonce, user.nonce))
+ {
+ // RFC 2069 format does not verify nc value - allow just once
+ if (user.counter == 1 || (!parsed->qop.empty() && parsed->counter() == user.counter))
+ {
+ return kPass;
+ }
+ }
+ return kStale;
+ }
+ return kFail;
+ }
+
+ //! \return Information needed to generate client authentication `response`s.
+ static http::http_client_auth::session::keys extract(
+ const http::http_response_info& response, const bool is_first)
+ {
+ using field = std::pair<std::string, std::string>;
+
+ server_parameters best{};
+
+ const std::list<field>& fields = response.m_additional_fields;
+ auto current = fields.begin();
+ const auto end = fields.end();
+ while (true)
+ {
+ current = std::find_if(current, end, [] (const field& value) {
+ return boost::equals(server_auth_field, value.first, ascii_iequal);
+ });
+ if (current == end)
+ break;
+
+ const auto parsed = parse(current->second);
+ if (parsed)
+ {
+ server_parameters local_best = parsed->algorithm.empty() ?
+ server_parameters{*parsed, boost::fusion::find<md5_>(digest_algorithms)} :
+ boost::fusion::iter_fold(digest_algorithms, server_parameters{}, matches_algorithm{*parsed});
+
+ if (local_best.index < best.index)
+ best = std::move(local_best);
+ }
+ ++current;
+ }
+ if (is_first || boost::equals(best.stale, ceref(u8"true"), ascii_iequal))
+ return best.take();
+ return {}; // authentication failed with bad user/pass
+ }
+
+ private:
+ explicit auth_message()
+ : algorithm()
+ , cnonce()
+ , nc()
+ , nonce()
+ , qop()
+ , realm()
+ , response()
+ , stale()
+ , uri()
+ , username() {
+ }
+
+ static boost::optional<auth_message> parse(const boost::string_ref request)
{
struct parser
{
- using field_parser = std::function<bool(const parser&, iterator&, iterator, auth_request&)>;
+ using field_parser = std::function<bool(const parser&, iterator&, iterator, auth_message&)>;
- explicit parser() : field_table(), skip_whitespace(), header(), token(), fields() {
+ explicit parser() : field_table(), skip_whitespace(), header(), quoted_string(), token(), fields() {
using namespace std::placeholders;
namespace qi = boost::spirit::qi;
struct parse_nc
{
- bool operator()(const parser&, iterator& current, const iterator end, auth_request& result) const
+ bool operator()(const parser&, iterator& current, const iterator end, auth_message& result) const
{
return qi::parse(
current, end,
@@ -212,19 +435,19 @@ namespace
};
struct parse_string
{
- bool operator()(const parser&, iterator& current, const iterator end,
+ bool operator()(const parser& parse, iterator& current, const iterator end,
boost::iterator_range<iterator>& result) const
{
- return qi::parse(
- current, end,
- (qi::lit(quote) >> qi::raw[+(u8"\\\"" | (qi::ascii::char_ - quote))] >> qi::lit(quote)),
- result
- );
+ return qi::parse(current, end, parse.quoted_string, result);
+ }
+ bool operator()(const parser& parse, iterator& current, const iterator end) const
+ {
+ return qi::parse(current, end, parse.quoted_string);
}
};
struct parse_response
{
- bool operator()(const parser&, iterator& current, const iterator end, auth_request& result) const
+ bool operator()(const parser&, iterator& current, const iterator end, auth_message& result) const
{
using byte = qi::uint_parser<std::uint8_t, 16, 2, 2>;
return qi::parse(
@@ -236,38 +459,41 @@ namespace
};
field_table.add
- (u8"algorithm", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_request::algorithm, _4)))
- (u8"cnonce", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_request::cnonce, _4)))
+ (u8"algorithm", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_message::algorithm, _4)))
+ (u8"cnonce", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::cnonce, _4)))
+ (u8"domain", std::bind(parse_string{}, _1, _2, _3)) // ignore field
(u8"nc", parse_nc{})
- (u8"nonce", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_request::nonce, _4)))
- (u8"qop", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_request::qop, _4)))
- (u8"realm", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_request::realm, _4)))
+ (u8"nonce", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::nonce, _4)))
+ (u8"opaque", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::opaque, _4)))
+ (u8"qop", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_message::qop, _4)))
+ (u8"realm", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::realm, _4)))
(u8"response", parse_response{})
- (u8"uri", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_request::uri, _4)))
- (u8"username", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_request::username, _4)));
+ (u8"stale", std::bind(parse_token{}, _1, _2, _3, std::bind(&auth_message::stale, _4)))
+ (u8"uri", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::uri, _4)))
+ (u8"username", std::bind(parse_string{}, _1, _2, _3, std::bind(&auth_message::username, _4)));
skip_whitespace = *(&qi::ascii::char_ >> qi::ascii::space);
header = skip_whitespace >> qi::ascii::no_case[u8"digest"] >> skip_whitespace;
+ quoted_string = (qi::lit(quote) >> qi::raw[+(u8"\\\"" | (qi::ascii::char_ - quote))] >> qi::lit(quote));
token =
- -qi::lit(quote) >>
- qi::raw[+(&qi::ascii::char_ >> (qi::ascii::graph - qi::ascii::char_(u8"()<>@,;:\\\"/[]?={}")))] >>
- -qi::lit(quote);
+ (!qi::lit(quote) >> qi::raw[+(&qi::ascii::char_ >> (qi::ascii::graph - qi::ascii::char_(u8"()<>@,;:\\\"/[]?={}")))]) |
+ quoted_string;
fields = field_table >> skip_whitespace >> equal_sign >> skip_whitespace;
}
- boost::optional<auth_request> operator()(const std::string& method, const std::string& request) const
+ boost::optional<auth_message> operator()(const boost::string_ref request) const
{
namespace qi = boost::spirit::qi;
- iterator current = request.data();
- const iterator end = current + request.size();
+ iterator current = request.begin();
+ const iterator end = request.end();
if (!qi::parse(current, end, header))
{
return boost::none;
}
- auth_request info(method);
+ auth_message info{};
field_parser null_parser{};
std::reference_wrapper<const field_parser> field = null_parser;
@@ -288,26 +514,13 @@ namespace
> field_table;
boost::spirit::qi::rule<iterator> skip_whitespace;
boost::spirit::qi::rule<iterator> header;
+ boost::spirit::qi::rule<iterator, boost::iterator_range<iterator>()> quoted_string;
boost::spirit::qi::rule<iterator, boost::iterator_range<iterator>()> token;
boost::spirit::qi::rule<iterator, std::reference_wrapper<const field_parser>()> fields;
}; // parser
- static const parser parse;
- return do_verify(parse(method, request), user);
- }
-
- private:
- explicit auth_request(const std::string& method_)
- : algorithm()
- , cnonce()
- , method(method_)
- , nc()
- , nonce()
- , qop()
- , realm()
- , response()
- , uri()
- , username() {
+ static const parser parse_;
+ return parse_(request);
}
struct has_valid_response
@@ -326,12 +539,6 @@ namespace
);
}
- template<typename Digest>
- typename std::result_of<Digest()>::type generate_auth(Digest digest) const
- {
- return digest(request.method, u8":", request.uri);
- }
-
template<typename Result>
bool check(const Result& result) const
{
@@ -344,96 +551,130 @@ namespace
if (boost::starts_with(request.algorithm, Digest::name, ascii_iequal) ||
(request.algorithm.empty() && std::is_same<md5_, Digest>::value))
{
- auto key = digest(user.credentials.username, u8":", auth_realm, u8":", user.credentials.password);
-
+ auto key = generate_a1(digest, user.credentials, auth_realm);
if (boost::ends_with(request.algorithm, sess_algo, ascii_iequal))
{
key = digest(key, u8":", request.nonce, u8":", request.cnonce);
}
+ auto auth = digest(method, u8":", request.uri);
if (request.qop.empty())
{
- return check(generate_old_response(digest, std::move(key), generate_auth(digest)));
+ return check(generate_old_response(std::move(digest), std::move(key), std::move(auth)));
}
else if (boost::equals(ceref(u8"auth"), request.qop, ascii_iequal))
{
- return check(generate_new_response(digest, std::move(key), generate_auth(digest)));
+ return check(generate_new_response(std::move(digest), std::move(key), std::move(auth)));
}
}
return false;
}
- const auth_request& request;
- const epee::net_utils::http::http_auth::session& user;
+ const auth_message& request;
+ const http::http_server_auth::session& user;
+ const boost::string_ref method;
};
- static status do_verify(const boost::optional<auth_request>& request,
- const epee::net_utils::http::http_auth::session& user)
+ boost::optional<std::uint32_t> counter() const
+ {
+ namespace qi = boost::spirit::qi;
+ using hex = qi::uint_parser<std::uint32_t, 16>;
+ std::uint32_t value = 0;
+ const bool converted = qi::parse(nc.begin(), nc.end(), hex{}, value);
+ return boost::make_optional(converted, value);
+ }
+
+ struct server_parameters
{
- if (request &&
- boost::equals(request->username, user.credentials.username) &&
- boost::fusion::any(digest_algorithms, has_valid_response{*request, user}))
+ server_parameters()
+ : nonce(), opaque(), realm(), stale(), value_generator()
+ , index(boost::fusion::size(digest_algorithms))
+ {}
+
+ template<typename DigestIter>
+ explicit server_parameters(const auth_message& request, const DigestIter& digest)
+ : nonce(request.nonce)
+ , opaque(request.opaque)
+ , stale(request.stale)
+ , realm(request.realm)
+ , value_generator()
+ , index(boost::fusion::distance(boost::fusion::begin(digest_algorithms), digest))
{
- if (boost::equals(request->nonce, user.nonce))
+ using digest_type = typename boost::fusion::result_of::value_of<DigestIter>::type;
+
+ // debug check internal state of the auth_message class
+ assert(
+ (std::is_same<digest_type, md5_>::value) ||
+ boost::equals((*digest).name, request.algorithm, ascii_iequal)
+ );
+ if (request.qop.empty())
+ value_generator = old_algorithm<digest_type>{*digest};
+ else
{
- // RFC 2069 format does not verify nc value - allow just once
- if (user.counter == 1 || (!request->qop.empty() && request->counter() == user.counter))
+ for (auto elem = boost::make_split_iterator(request.qop, boost::token_finder(http_list_separator));
+ !elem.eof();
+ ++elem)
{
- return kPass;
+ if (boost::equals(ceref(u8"auth"), *elem, ascii_iequal))
+ {
+ value_generator = auth_algorithm<digest_type>{*digest};
+ break;
+ }
}
+ if (!value_generator) // no supported qop mode
+ index = boost::fusion::size(digest_algorithms);
}
- return kStale;
}
- return kFail;
- }
- boost::optional<std::uint32_t> counter() const
+ http::http_client_auth::session::keys take()
+ {
+ return {to_string(nonce), to_string(opaque), to_string(realm), std::move(value_generator)};
+ }
+
+ boost::iterator_range<iterator> nonce;
+ boost::iterator_range<iterator> opaque;
+ boost::iterator_range<iterator> realm;
+ boost::iterator_range<iterator> stale;
+ http::http_client_auth::session::keys::algorithm value_generator;
+ unsigned index;
+ };
+
+ struct matches_algorithm
{
- namespace qi = boost::spirit::qi;
- using hex = qi::uint_parser<std::uint32_t, 16>;
- std::uint32_t value = 0;
- const bool converted = qi::parse(nc.begin(), nc.end(), hex{}, value);
- return boost::make_optional(converted, value);
- }
+ template<typename DigestIter>
+ server_parameters operator()(server_parameters current, const DigestIter& digest) const
+ {
+ if (!current.value_generator)
+ {
+ if (boost::equals(response.algorithm, (*digest).name, ascii_iequal))
+ {
+ current = server_parameters{response, digest};
+ }
+ }
+ return current;
+ }
+ const auth_message& response;
+ };
+
boost::iterator_range<iterator> algorithm;
boost::iterator_range<iterator> cnonce;
- boost::string_ref method;
boost::iterator_range<iterator> nc;
boost::iterator_range<iterator> nonce;
+ boost::iterator_range<iterator> opaque;
boost::iterator_range<iterator> qop;
boost::iterator_range<iterator> realm;
boost::iterator_range<iterator> response;
+ boost::iterator_range<iterator> stale;
boost::iterator_range<iterator> uri;
boost::iterator_range<iterator> username;
- }; // auth_request
+ }; // auth_message
struct add_challenge
{
- template<typename T>
- static void add_field(std::string& str, const char* const name, const T& value)
- {
- str.push_back(comma);
- str.append(name);
- str.push_back(equal_sign);
- boost::range::copy(value, std::back_inserter(str));
- }
-
- template<typename T>
- using quoted_result = boost::joined_range<
- const boost::joined_range<const boost::string_ref, const T>, const boost::string_ref
- >;
-
- template<typename T>
- static quoted_result<T> quoted(const T& arg)
- {
- return boost::range::join(boost::range::join(ceref(u8"\""), arg), ceref(u8"\""));
- }
-
template<typename Digest>
void operator()(const Digest& digest) const
{
- static constexpr const auto fname = ceref(u8"WWW-authenticate");
static constexpr const auto fvalue = ceref(u8"Digest qop=\"auth\"");
for (unsigned i = 0; i < 2; ++i)
@@ -448,17 +689,16 @@ namespace
add_field(out, u8"nonce", quoted(nonce));
add_field(out, u8"stale", is_stale ? ceref("true") : ceref("false"));
- fields.push_back(std::make_pair(std::string(fname), std::move(out)));
+ fields.push_back(std::make_pair(std::string(server_auth_field), std::move(out)));
}
}
- const std::string& nonce;
+ const boost::string_ref nonce;
std::list<std::pair<std::string, std::string>>& fields;
const bool is_stale;
};
- epee::net_utils::http::http_response_info create_digest_response(
- const std::string& nonce, const bool is_stale)
+ http::http_response_info create_digest_response(const boost::string_ref nonce, const bool is_stale)
{
epee::net_utils::http::http_response_info rc{};
rc.m_response_code = 401;
@@ -481,35 +721,35 @@ namespace epee
{
namespace http
{
- http_auth::http_auth(login credentials)
+ http_server_auth::http_server_auth(login credentials)
: user(session{std::move(credentials)}) {
}
- boost::optional<http_response_info> http_auth::process(const http_request_info& request)
+ boost::optional<http_response_info> http_server_auth::do_get_response(const http_request_info& request)
{
assert(user);
using field = std::pair<std::string, std::string>;
const std::list<field>& fields = request.m_header_info.m_etc_fields;
const auto auth = boost::find_if(fields, [] (const field& value) {
- return boost::equals(ceref(u8"authorization"), value.first, ascii_iequal);
+ return boost::equals(client_auth_field, value.first, ascii_iequal);
});
bool is_stale = false;
if (auth != fields.end())
{
++(user->counter);
- switch (auth_request::verify(request.m_http_method_str, auth->second, *user))
+ switch (auth_message::verify(request.m_http_method_str, auth->second, *user))
{
- case auth_request::kPass:
+ case auth_message::kPass:
return boost::none;
- case auth_request::kStale:
+ case auth_message::kStale:
is_stale = true;
break;
default:
- case auth_request::kFail:
+ case auth_message::kFail:
break;
}
}
@@ -521,6 +761,35 @@ namespace epee
}
return create_digest_response(user->nonce, is_stale);
}
+
+ http_client_auth::http_client_auth(login credentials)
+ : user(session{std::move(credentials)}) {
+ }
+
+ http_client_auth::status http_client_auth::do_handle_401(const http_response_info& response)
+ {
+ assert(user);
+ const bool first_auth = (user->counter == 0);
+ user->server = auth_message::extract(response, first_auth);
+ if (user->server.generator)
+ {
+ user->counter = 0;
+ return kSuccess;
+ }
+ return first_auth ? kParseFailure : kBadPassword;
+ }
+
+ boost::optional<std::pair<std::string, std::string>> http_client_auth::do_get_auth_field(
+ const boost::string_ref method, const boost::string_ref uri)
+ {
+ assert(user);
+ if (user->server.generator)
+ {
+ ++(user->counter);
+ return std::make_pair(std::string(client_auth_field), user->server.generator(*user, method, uri));
+ }
+ return boost::none;
+ }
}
}
}
diff --git a/contrib/otshell_utils/CMakeLists.txt b/contrib/otshell_utils/CMakeLists.txt
index 464b125e4..263f07c85 100644
--- a/contrib/otshell_utils/CMakeLists.txt
+++ b/contrib/otshell_utils/CMakeLists.txt
@@ -3,6 +3,10 @@ project (otshell CXX)
# Add executable
+if(APPLE AND POLICY CMP0042)
+ cmake_policy(SET CMP0042 NEW)
+endif()
+
file(GLOB otshell_utils_sources # All files in directory:
"*.h"
"*.hpp"
diff --git a/external/boost/archive/portable_binary_archive.hpp b/external/boost/archive/portable_binary_archive.hpp
index 560ef121b..e940c5b9e 100644
--- a/external/boost/archive/portable_binary_archive.hpp
+++ b/external/boost/archive/portable_binary_archive.hpp
@@ -14,6 +14,7 @@
#include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include <boost/static_assert.hpp>
+#include <boost/archive/archive_exception.hpp>
#include <climits>
#if CHAR_BIT != 8
@@ -37,7 +38,9 @@ enum portable_binary_archive_flags {
//#endif
inline void
-reverse_bytes(char size, char *address){
+reverse_bytes(signed char size, char *address){
+ if (size <= 0)
+ throw archive_exception(archive_exception::other_exception);
char * first = address;
char * last = first + size - 1;
for(;first < last;++first, --last){
diff --git a/external/boost/archive/portable_binary_iarchive.hpp b/external/boost/archive/portable_binary_iarchive.hpp
index 7149cca0f..7792b530d 100644
--- a/external/boost/archive/portable_binary_iarchive.hpp
+++ b/external/boost/archive/portable_binary_iarchive.hpp
@@ -234,7 +234,7 @@ namespace boost { namespace archive {
inline void
portable_binary_iarchive::load_impl(boost::intmax_t & l, char maxsize){
- char size;
+ signed char size;
l = 0;
this->primitive_base_t::load(size);
@@ -321,6 +321,8 @@ portable_binary_iarchive::init(unsigned int flags){
boost::archive::library_version_type input_library_version;
* this >> input_library_version;
+ // ignore archive version checking
+ /*
// extra little .t is to get around borland quirk
if(boost::archive::BOOST_ARCHIVE_VERSION() < input_library_version)
boost::serialization::throw_exception(
@@ -328,6 +330,7 @@ portable_binary_iarchive::init(unsigned int flags){
boost::archive::archive_exception::unsupported_version
)
);
+ */
#if BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3205))
this->set_library_version(input_library_version);
@@ -354,11 +357,11 @@ namespace detail {
template class archive_serializer_map<portable_binary_iarchive>;
}
-template class basic_binary_iprimitive<
- portable_binary_iarchive,
- std::istream::char_type,
- std::istream::traits_type
-> ;
+// template class basic_binary_iprimitive<
+// portable_binary_iarchive,
+// std::istream::char_type,
+// std::istream::traits_type
+//> ;
} // namespace archive
} // namespace boost
diff --git a/external/boost/archive/portable_binary_oarchive.hpp b/external/boost/archive/portable_binary_oarchive.hpp
index 8fd7090a1..9ed30d064 100644
--- a/external/boost/archive/portable_binary_oarchive.hpp
+++ b/external/boost/archive/portable_binary_oarchive.hpp
@@ -225,7 +225,7 @@ portable_binary_oarchive::save_impl(
const boost::intmax_t l,
const char maxsize
){
- char size = 0;
+ signed char size = 0;
if(l == 0){
this->primitive_base_t::save(size);
@@ -245,7 +245,7 @@ portable_binary_oarchive::save_impl(
}while(ll != 0);
this->primitive_base_t::save(
- static_cast<char>(negative ? -size : size)
+ static_cast<signed char>(negative ? -size : size)
);
if(negative)
@@ -277,10 +277,14 @@ portable_binary_oarchive::init(unsigned int flags) {
boost::archive::BOOST_ARCHIVE_SIGNATURE()
);
* this << file_signature;
+ // ignore archive version checking
+ const boost::archive::library_version_type v{};
+ /*
// write library version
const boost::archive::library_version_type v(
boost::archive::BOOST_ARCHIVE_VERSION()
);
+ */
* this << v;
}
save(static_cast<unsigned char>(m_flags >> CHAR_BIT));
@@ -295,11 +299,11 @@ namespace detail {
template class archive_serializer_map<portable_binary_oarchive>;
}
-template class basic_binary_oprimitive<
- portable_binary_oarchive,
- std::ostream::char_type,
- std::ostream::traits_type
-> ;
+// template class basic_binary_oprimitive<
+// portable_binary_oarchive,
+// std::ostream::char_type,
+// std::ostream::traits_type
+// > ;
} // namespace archive
} // namespace boost
diff --git a/external/db_drivers/liblmdb/CMakeLists.txt b/external/db_drivers/liblmdb/CMakeLists.txt
index 00b203e94..3d55f0eae 100644
--- a/external/db_drivers/liblmdb/CMakeLists.txt
+++ b/external/db_drivers/liblmdb/CMakeLists.txt
@@ -30,6 +30,11 @@ if(FREEBSD)
add_definitions(-DMDB_DSYNC=O_SYNC)
endif()
+if(ANDROID)
+ add_definitions("-DANDROID=1")
+endif()
+
+
set (lmdb_sources
mdb.c
midl.c)
diff --git a/external/unbound/compat/getentropy_linux.c b/external/unbound/compat/getentropy_linux.c
index 37d86a8f1..b8d4a2bd0 100644
--- a/external/unbound/compat/getentropy_linux.c
+++ b/external/unbound/compat/getentropy_linux.c
@@ -30,7 +30,13 @@
#ifdef HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif
+#ifdef __ANDROID__
+#include <sys/vfs.h>
+#define statvfs statfs
+#define fstatvfs fstatfs
+#else
#include <sys/statvfs.h>
+#endif
#include <sys/socket.h>
#include <sys/mount.h>
#include <sys/mman.h>
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 91c388de6..b39cb1801 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -1020,6 +1020,20 @@ public:
virtual transaction get_tx(const crypto::hash& h) const = 0;
/**
+ * @brief fetches the transaction with the given hash
+ *
+ * The subclass should return the transaction stored which has the given
+ * hash.
+ *
+ * If the transaction does not exist, the subclass should return false.
+ *
+ * @param h the hash to look for
+ *
+ * @return true iff the transaction was found
+ */
+ virtual bool get_tx(const crypto::hash& h, transaction &tx) const = 0;
+
+ /**
* @brief fetches the total number of transactions ever
*
* The subclass should return a count of all the transactions from
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 1ad9876ac..ba2cb60bd 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -1791,7 +1791,7 @@ uint64_t BlockchainLMDB::get_tx_unlock_time(const crypto::hash& h) const
return ret;
}
-transaction BlockchainLMDB::get_tx(const crypto::hash& h) const
+bool BlockchainLMDB::get_tx(const crypto::hash& h, transaction &tx) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
@@ -1810,19 +1810,27 @@ transaction BlockchainLMDB::get_tx(const crypto::hash& h) const
get_result = mdb_cursor_get(m_cur_txs, &val_tx_id, &result, MDB_SET);
}
if (get_result == MDB_NOTFOUND)
- throw1(TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str()));
+ return false;
else if (get_result)
throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx from hash", get_result).c_str()));
blobdata bd;
bd.assign(reinterpret_cast<char*>(result.mv_data), result.mv_size);
- transaction tx;
if (!parse_and_validate_tx_from_blob(bd, tx))
throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db"));
TXN_POSTFIX_RDONLY();
+ return true;
+}
+
+transaction BlockchainLMDB::get_tx(const crypto::hash& h) const
+{
+ transaction tx;
+
+ if (!get_tx(h, tx))
+ throw1(TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str()));
return tx;
}
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index 6db5abca1..b0d8d9d0a 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -209,6 +209,8 @@ public:
virtual transaction get_tx(const crypto::hash& h) const;
+ virtual bool get_tx(const crypto::hash& h, transaction &tx) const;
+
virtual uint64_t get_tx_count() const;
virtual std::vector<transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const;
diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp
index e6e53a5c0..35fb9fe6c 100644
--- a/src/common/dns_utils.cpp
+++ b/src/common/dns_utils.cpp
@@ -26,7 +26,10 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#include "common/command_line.h"
+#include "common/i18n.h"
#include "common/dns_utils.h"
+#include "cryptonote_core/cryptonote_basic_impl.h"
#include <cstring>
#include <sstream>
// check local first (in the event of static or in-source compilation of libunbound)
@@ -323,4 +326,144 @@ bool DNSResolver::check_address_syntax(const char *addr) const
return true;
}
+namespace dns_utils
+{
+
+const char *tr(const char *str) { return i18n_translate(str, "tools::dns_utils"); }
+
+//-----------------------------------------------------------------------
+// TODO: parse the string in a less stupid way, probably with regex
+std::string address_from_txt_record(const std::string& s)
+{
+ // make sure the txt record has "oa1:xmr" and find it
+ auto pos = s.find("oa1:xmr");
+ if (pos == std::string::npos)
+ return {};
+ // search from there to find "recipient_address="
+ pos = s.find("recipient_address=", pos);
+ if (pos == std::string::npos)
+ return {};
+ pos += 18; // move past "recipient_address="
+ // find the next semicolon
+ auto pos2 = s.find(";", pos);
+ if (pos2 != std::string::npos)
+ {
+ // length of address == 95, we can at least validate that much here
+ if (pos2 - pos == 95)
+ {
+ return s.substr(pos, 95);
+ }
+ else if (pos2 - pos == 106) // length of address == 106 --> integrated address
+ {
+ return s.substr(pos, 106);
+ }
+ }
+ return {};
+}
+/**
+ * @brief gets a monero address from the TXT record of a DNS entry
+ *
+ * gets the monero address from the TXT record of the DNS entry associated
+ * with <url>. If this lookup fails, or the TXT record does not contain an
+ * XMR address in the correct format, returns an empty string. <dnssec_valid>
+ * will be set true or false according to whether or not the DNS query passes
+ * DNSSEC validation.
+ *
+ * @param url the url to look up
+ * @param dnssec_valid return-by-reference for DNSSEC status of query
+ *
+ * @return a monero address (as a string) or an empty string
+ */
+std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid)
+{
+ std::vector<std::string> addresses;
+ // get txt records
+ bool dnssec_available, dnssec_isvalid;
+ std::string oa_addr = DNSResolver::instance().get_dns_format_from_oa_address(url);
+ auto records = DNSResolver::instance().get_txt_record(oa_addr, dnssec_available, dnssec_isvalid);
+
+ // TODO: update this to allow for conveying that dnssec was not available
+ if (dnssec_available && dnssec_isvalid)
+ {
+ dnssec_valid = true;
+ }
+ else dnssec_valid = false;
+
+ // for each txt record, try to find a monero address in it.
+ for (auto& rec : records)
+ {
+ std::string addr = address_from_txt_record(rec);
+ if (addr.size())
+ {
+ addresses.push_back(addr);
+ }
+ }
+ return addresses;
+}
+
+std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid)
+{
+ // attempt to get address from dns query
+ auto addresses = addresses_from_url(url, dnssec_valid);
+ if (addresses.empty())
+ {
+ std::cout << tr("wrong address: ") << url;
+ return {};
+ }
+ // for now, move on only if one address found
+ if (addresses.size() > 1)
+ {
+ std::cout << tr("not yet supported: Multiple Monero addresses found for given URL: ") << url;
+ return {};
+ }
+ // prompt user for confirmation.
+ // inform user of DNSSEC validation status as well.
+ std::string dnssec_str;
+ if (dnssec_valid)
+ {
+ dnssec_str = tr("DNSSEC validation passed");
+ }
+ else
+ {
+ dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!");
+ }
+ std::stringstream prompt;
+ prompt << tr("For URL: ") << url
+ << ", " << dnssec_str << std::endl
+ << tr(" Monero Address = ") << addresses[0]
+ << std::endl
+ << tr("Is this OK? (Y/n) ")
+ ;
+ // prompt the user for confirmation given the dns query and dnssec status
+ std::string confirm_dns_ok = command_line::input_line(prompt.str());
+ if (std::cin.eof())
+ {
+ return {};
+ }
+ if (!command_line::is_yes(confirm_dns_ok))
+ {
+ std::cout << tr("you have cancelled the transfer request") << std::endl;
+ return {};
+ }
+ return addresses[0];
+}
+
+bool get_account_address_from_str_or_url(
+ cryptonote::account_public_address& address
+ , bool& has_payment_id
+ , crypto::hash8& payment_id
+ , bool testnet
+ , const std::string& str_or_url
+ )
+{
+ if (cryptonote::get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, str_or_url))
+ return true;
+ bool dnssec_valid;
+ std::string address_str = get_account_address_as_str_from_url(str_or_url, dnssec_valid);
+ return !address_str.empty() &&
+ cryptonote::get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_str);
+}
+
+} // namespace tools::dns_utils
+
} // namespace tools
diff --git a/src/common/dns_utils.h b/src/common/dns_utils.h
index 2e87fb01f..5fe1d4775 100644
--- a/src/common/dns_utils.h
+++ b/src/common/dns_utils.h
@@ -25,9 +25,11 @@
// 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 <vector>
#include <string>
+#include "cryptonote_core/cryptonote_basic.h"
namespace tools
{
@@ -155,4 +157,21 @@ private:
DNSResolverData *m_data;
}; // class DNSResolver
+namespace dns_utils
+{
+
+std::string address_from_txt_record(const std::string& s);
+std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid);
+
+std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid);
+bool get_account_address_from_str_or_url(
+ cryptonote::account_public_address& address
+ , bool& has_payment_id
+ , crypto::hash8& payment_id
+ , bool testnet
+ , const std::string& str_or_url
+ );
+
+} // namespace tools::dns_utils
+
} // namespace tools
diff --git a/src/common/int-util.h b/src/common/int-util.h
index e9eddee9c..9ac20e582 100644
--- a/src/common/int-util.h
+++ b/src/common/int-util.h
@@ -36,6 +36,10 @@
#include <string.h>
#include <sys/param.h>
+#if defined(__ANDROID__)
+#include <byteswap.h>
+#endif
+
#if defined(_MSC_VER)
#include <stdlib.h>
@@ -138,16 +142,24 @@ static inline uint32_t ident32(uint32_t x) { return x; }
static inline uint64_t ident64(uint64_t x) { return x; }
#ifndef __OpenBSD__
+# if defined(__ANDROID__) && defined(__swap32) && !defined(swap32)
+# define swap32 __swap32
+# elif !defined(swap32)
static inline uint32_t swap32(uint32_t x) {
x = ((x & 0x00ff00ff) << 8) | ((x & 0xff00ff00) >> 8);
return (x << 16) | (x >> 16);
}
+# endif
+# if defined(__ANDROID__) && defined(__swap64) && !defined(swap64)
+# define swap64 __swap64
+# elif !defined(swap64)
static inline uint64_t swap64(uint64_t x) {
x = ((x & 0x00ff00ff00ff00ff) << 8) | ((x & 0xff00ff00ff00ff00) >> 8);
x = ((x & 0x0000ffff0000ffff) << 16) | ((x & 0xffff0000ffff0000) >> 16);
return (x << 32) | (x >> 32);
}
-#endif
+# endif
+#endif /* __OpenBSD__ */
#if defined(__GNUC__)
#define UNUSED __attribute__((unused))
diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt
index 9d83caca8..1e037a07d 100644
--- a/src/crypto/CMakeLists.txt
+++ b/src/crypto/CMakeLists.txt
@@ -89,3 +89,14 @@ if (ARM)
PROPERTY COMPILE_DEFINITIONS "NO_OPTIMIZED_MULTIPLY_ON_ARM")
endif()
endif()
+
+# Because of the way Qt works on android with JNI, the code does not live in the main android thread
+# So this code runs with a 1 MB default stack size.
+# This will force the use of the heap for the allocation of the scratchpad
+if (ANDROID)
+ if( BUILD_GUI_DEPS )
+ add_definitions(-DFORCE_USE_HEAP=1)
+ endif()
+endif()
+
+
diff --git a/src/crypto/oaes_lib.c b/src/crypto/oaes_lib.c
index f054a16f4..0afec6212 100644
--- a/src/crypto/oaes_lib.c
+++ b/src/crypto/oaes_lib.c
@@ -39,8 +39,8 @@
#include <malloc.h>
#endif
-// FreeBSD, and OpenBSD also don't need timeb.h
-#if !defined(__FreeBSD__) && !defined(__OpenBSD__)
+// ANDROID, FreeBSD, and OpenBSD also don't need timeb.h
+#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__ANDROID__)
#include <sys/timeb.h>
#else
#include <sys/time.h>
@@ -499,7 +499,7 @@ static void oaes_get_seed( char buf[RANDSIZ + 1] )
#else
static uint32_t oaes_get_seed(void)
{
- #if !defined(__FreeBSD__) && !defined(__OpenBSD__)
+ #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__ANDROID__)
struct timeb timer;
struct tm *gmTimer;
char * _test = NULL;
diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c
index 66d9ca5d9..43b9619f3 100644
--- a/src/crypto/slow-hash.c
+++ b/src/crypto/slow-hash.c
@@ -1052,7 +1052,6 @@ STATIC INLINE void xor_blocks(uint8_t* a, const uint8_t* b)
void cn_slow_hash(const void *data, size_t length, char *hash)
{
- uint8_t long_state[MEMORY];
uint8_t text[INIT_SIZE_BYTE];
uint8_t a[AES_BLOCK_SIZE];
uint8_t b[AES_BLOCK_SIZE];
@@ -1070,6 +1069,13 @@ void cn_slow_hash(const void *data, size_t length, char *hash)
hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein
};
+#ifndef FORCE_USE_HEAP
+ uint8_t long_state[MEMORY];
+#else
+ uint8_t *long_state = NULL;
+ long_state = (uint8_t *)malloc(MEMORY);
+#endif
+
hash_process(&state.hs, data, length);
memcpy(text, state.init, INIT_SIZE_BYTE);
@@ -1129,6 +1135,9 @@ void cn_slow_hash(const void *data, size_t length, char *hash)
memcpy(state.init, text, INIT_SIZE_BYTE);
hash_permutation(&state.hs);
extra_hashes[state.hs.b[0] & 3](&state, 200, hash);
+#ifdef FORCE_USE_HEAP
+ free(long_state);
+#endif
}
#endif /* !aarch64 || !crypto */
diff --git a/src/crypto/tree-hash.c b/src/crypto/tree-hash.c
index d73f0d959..5cdaa8c94 100644
--- a/src/crypto/tree-hash.c
+++ b/src/crypto/tree-hash.c
@@ -40,27 +40,28 @@
#include <stdlib.h>
#endif
-/// Quick check if this is power of two (use on unsigned types; in this case for size_t only)
-bool ispowerof2_size_t(size_t x) {
- return x && !(x & (x - 1));
-}
-
/***
* Round to power of two, for count>=3 and for count being not too large (as reasonable for tree hash calculations)
*/
size_t tree_hash_cnt(size_t count) {
- assert( count >= 3); // cases for 0,1,2 are handled elsewhere
- // Round down the count size: fun(2**n)= 2**(n-1) to round down to power of two
- size_t tmp = count - 1;
- size_t jj = 1;
- for (jj=1 ; tmp != 0 ; ++jj) {
- tmp /= 2; // dividing by 2 until to get how many powers of 2 fits size_to tmp
- }
- size_t cnt = 1 << (jj-2); // cnt is the count, but rounded down to power of two
- // printf("count=%zu cnt=%zu jj=%zu tmp=%zu \n" , count,cnt,jj,tmp);
- assert( cnt > 0 ); assert( cnt >= count/2 ); assert( cnt <= count );
- assert( ispowerof2_size_t( cnt ));
- return cnt;
+ // This algo has some bad history but all we are doing is 1 << floor(log2(count))
+ // There are _many_ ways to do log2, for some reason the one selected was the most obscure one,
+ // and fixing it made it even more obscure.
+ //
+ // Iterative method implemented below aims for clarity over speed, if performance is needed
+ // then my advice is to use the BSR instruction on x86
+ //
+ // All the paranoid asserts have been removed since it is trivial to mathematically prove that
+ // the return will always be a power of 2.
+ // Problem space has been defined as 3 <= count <= 2^28. Of course quarter of a billion transactions
+ // is not a sane upper limit for a block, so there will be tighter limits in other parts of the code
+
+ assert( count >= 3 ); // cases for 0,1,2 are handled elsewhere
+ assert( count <= 0x10000000 ); // sanity limit to 2^28, MSB=1 will cause an inf loop
+
+ size_t pow = 2;
+ while(pow < count) pow <<= 1;
+ return pow >> 1;
}
void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) {
@@ -86,9 +87,6 @@ void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) {
size_t i, j;
size_t cnt = tree_hash_cnt( count );
- size_t max_size_t = (size_t) -1; // max allowed value of size_t
- assert( cnt < max_size_t/2 ); // reasonable size to avoid any overflows. /2 is extra; Anyway should be limited much stronger by logical code
- // as we have sane limits on transactions counts in blockchain rules
char (*ints)[HASH_SIZE];
size_t ints_size = cnt * HASH_SIZE;
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 42279184a..e27261c46 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -525,6 +525,7 @@ bool Blockchain::reset_and_set_genesis_block(const block& b)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
+ m_timestamps_and_difficulties_height = 0;
m_alternative_chains.clear();
m_db->reset();
m_hardfork->init();
@@ -1084,7 +1085,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m
size_t txs_size;
uint64_t fee;
- if (!m_tx_pool.fill_block_template(b, median_size, already_generated_coins, txs_size, fee))
+ if (!m_tx_pool.fill_block_template(b, median_size, already_generated_coins, txs_size, fee, m_hardfork->get_current_version()))
{
return false;
}
@@ -1777,7 +1778,7 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMA
tx_out_index toi = m_db->get_output_tx_and_index(i.amount, i.index);
bool unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first));
- res.outs.push_back({od.pubkey, od.commitment, unlocked});
+ res.outs.push_back({od.pubkey, od.commitment, unlocked, od.height, toi.first});
}
return true;
}
@@ -1895,11 +1896,11 @@ bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container
{
try
{
- txs.push_back(m_db->get_tx(tx_hash));
- }
- catch (const TX_DNE& e)
- {
- missed_txs.push_back(tx_hash);
+ transaction tx;
+ if (m_db->get_tx(tx_hash, tx))
+ txs.push_back(std::move(tx));
+ else
+ missed_txs.push_back(tx_hash);
}
catch (const std::exception& e)
{
diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h
index 704b8467d..a6610e60d 100644
--- a/src/cryptonote_core/cryptonote_format_utils.h
+++ b/src/cryptonote_core/cryptonote_format_utils.h
@@ -36,7 +36,8 @@
#include "crypto/crypto.h"
#include "crypto/hash.h"
#include "ringct/rctOps.h"
-
+#include <boost/serialization/vector.hpp>
+#include <boost/serialization/utility.hpp>
namespace cryptonote
{
@@ -62,16 +63,6 @@ namespace cryptonote
rct::key mask; //ringct amount mask
void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); }
-
- BEGIN_SERIALIZE_OBJECT()
- FIELD(outputs)
- VARINT_FIELD(real_output)
- FIELD(real_out_tx_key)
- VARINT_FIELD(real_output_in_tx_index)
- VARINT_FIELD(amount)
- FIELD(rct)
- FIELD(mask)
- END_SERIALIZE()
};
struct tx_destination_entry
@@ -261,3 +252,23 @@ namespace cryptonote
specific_type& variable_name = boost::get<specific_type>(variant_var);
}
+
+BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0)
+
+namespace boost
+{
+ namespace serialization
+ {
+ template <class Archive>
+ inline void serialize(Archive &a, cryptonote::tx_source_entry &x, const boost::serialization::version_type ver)
+ {
+ a & x.outputs;
+ a & x.real_output;
+ a & x.real_out_tx_key;
+ a & x.real_output_in_tx_index;
+ a & x.amount;
+ a & x.rct;
+ a & x.mask;
+ }
+ }
+}
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index 4cfd61f9f..59ac534fe 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -60,6 +60,7 @@ namespace cryptonote
size_t const TRANSACTION_SIZE_LIMIT_V2 = (((CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE);
time_t const MIN_RELAY_TIME = (60 * 5); // only start re-relaying transactions after that many seconds
time_t const MAX_RELAY_TIME = (60 * 60 * 4); // at most that many seconds between resends
+ float const ACCEPT_THRESHOLD = 0.99f;
// a kind of increasing backoff within min/max bounds
time_t get_relay_delay(time_t now, time_t received)
@@ -69,6 +70,11 @@ namespace cryptonote
d = MAX_RELAY_TIME;
return d;
}
+
+ uint64_t template_accept_threshold(uint64_t amount)
+ {
+ return amount * ACCEPT_THRESHOLD;
+ }
}
//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
@@ -189,6 +195,8 @@ namespace cryptonote
txd_p.first->second.fee = fee;
txd_p.first->second.max_used_block_id = null_hash;
txd_p.first->second.max_used_block_height = 0;
+ txd_p.first->second.last_failed_height = 0;
+ txd_p.first->second.last_failed_id = null_hash;
txd_p.first->second.kept_by_block = kept_by_block;
txd_p.first->second.receive_time = time(nullptr);
txd_p.first->second.last_relayed_time = time(NULL);
@@ -585,7 +593,7 @@ namespace cryptonote
}
//---------------------------------------------------------------------------------
//TODO: investigate whether boolean return is appropriate
- bool tx_memory_pool::fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee)
+ bool tx_memory_pool::fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee, uint8_t version)
{
// Warning: This function takes already_generated_
// coins as an argument and appears to do nothing
@@ -593,47 +601,51 @@ namespace cryptonote
CRITICAL_REGION_LOCAL(m_transactions_lock);
+ uint64_t best_coinbase = 0;
total_size = 0;
fee = 0;
- // Maximum block size is 130% of the median block size. This gives a
- // little extra headroom for the max size transaction.
- size_t max_total_size = (130 * median_size) / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
+ size_t max_total_size = 2 * median_size - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
std::unordered_set<crypto::key_image> k_images;
+ LOG_PRINT_L2("Filling block template, median size " << median_size << ", " << m_txs_by_fee.size() << " txes in the pool");
auto sorted_it = m_txs_by_fee.begin();
while (sorted_it != m_txs_by_fee.end())
{
auto tx_it = m_transactions.find(sorted_it->second);
+ LOG_PRINT_L2("Considering " << tx_it->first << ", size " << tx_it->second.blob_size << ", current block size " << total_size << "/" << max_total_size << ", current coinbase " << print_money(best_coinbase));
// Can not exceed maximum block size
if (max_total_size < total_size + tx_it->second.blob_size)
{
+ LOG_PRINT_L2(" would exceed maximum block size");
sorted_it++;
continue;
}
- // If adding this tx will make the block size
- // greater than CRYPTONOTE_GETBLOCKTEMPLATE_MAX
- // _BLOCK_SIZE bytes, reject the tx; this will
- // keep block sizes from becoming too unwieldly
- // to propagate at 60s block times.
- if ( (total_size + tx_it->second.blob_size) > CRYPTONOTE_GETBLOCKTEMPLATE_MAX_BLOCK_SIZE )
+ // If we're getting lower coinbase tx,
+ // stop including more tx
+ uint64_t block_reward;
+ if(!get_block_reward(median_size, total_size + tx_it->second.blob_size, already_generated_coins, block_reward, version))
{
+ LOG_PRINT_L2(" would exceed maximum block size");
+ sorted_it++;
+ continue;
+ }
+ uint64_t coinbase = block_reward + fee;
+ if (coinbase < template_accept_threshold(best_coinbase))
+ {
+ LOG_PRINT_L2(" would decrease coinbase to " << print_money(coinbase));
sorted_it++;
continue;
}
-
- // If we've exceeded the penalty free size,
- // stop including more tx
- if (total_size > median_size)
- break;
// Skip transactions that are not ready to be
// included into the blockchain or that are
// missing key images
if (!is_transaction_ready_to_go(tx_it->second) || have_key_images(k_images, tx_it->second.tx))
{
+ LOG_PRINT_L2(" not ready to go, or key images already seen");
sorted_it++;
continue;
}
@@ -641,10 +653,15 @@ namespace cryptonote
bl.tx_hashes.push_back(tx_it->first);
total_size += tx_it->second.blob_size;
fee += tx_it->second.fee;
+ best_coinbase = coinbase;
append_key_images(k_images, tx_it->second.tx);
sorted_it++;
+ LOG_PRINT_L2(" added, new block size " << total_size << "/" << max_total_size << ", coinbase " << print_money(best_coinbase));
}
+ LOG_PRINT_L2("Block template filled with " << bl.tx_hashes.size() << " txes, size "
+ << total_size << "/" << max_total_size << ", coinbase " << print_money(best_coinbase)
+ << " (including " << print_money(fee) << " in fees)");
return true;
}
//---------------------------------------------------------------------------------
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index 794b86719..32a05b0f4 100644
--- a/src/cryptonote_core/tx_pool.h
+++ b/src/cryptonote_core/tx_pool.h
@@ -216,10 +216,11 @@ namespace cryptonote
* @param already_generated_coins the current total number of coins "minted"
* @param total_size return-by-reference the total size of the new block
* @param fee return-by-reference the total of fees from the included transactions
+ * @param version hard fork version to use for consensus rules
*
* @return true
*/
- bool fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee);
+ bool fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee, uint8_t version);
/**
* @brief get a list of all transactions in the pool
diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp
index f07ef1616..7381dd06f 100644
--- a/src/daemon/command_parser_executor.cpp
+++ b/src/daemon/command_parser_executor.cpp
@@ -26,7 +26,7 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#include "cryptonote_core/cryptonote_basic_impl.h"
+#include "common/dns_utils.h"
#include "daemon/command_parser_executor.h"
namespace daemonize {
@@ -238,17 +238,35 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg
}
cryptonote::account_public_address adr;
+ bool has_payment_id;
+ crypto::hash8 payment_id;
bool testnet = false;
- if(!cryptonote::get_account_address_from_str(adr, false, args.front()))
+ if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, args.front()))
{
- if(!cryptonote::get_account_address_from_str(adr, true, args.front()))
+ if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, args.front()))
{
- std::cout << "target account address has wrong format" << std::endl;
- return true;
+ bool dnssec_valid;
+ std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(args.front(), dnssec_valid);
+ if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, address_str))
+ {
+ if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, address_str))
+ {
+ std::cout << "target account address has wrong format" << std::endl;
+ return true;
+ }
+ else
+ {
+ testnet = true;
+ }
+ }
+ }
+ else
+ {
+ testnet = true;
}
- testnet = true;
- std::cout << "Mining to a testnet address, make sure this is intentional!" << std::endl;
}
+ if(testnet)
+ std::cout << "Mining to a testnet address, make sure this is intentional!" << std::endl;
uint64_t threads_count = 1;
if(args.size() > 2)
{
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index 41b1fad24..4d415b4c2 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -473,8 +473,9 @@ bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, u
if (!first)
std::cout << std::endl;
std::cout
+ << "height: " << header.height << ", timestamp: " << header.timestamp << ", difficulty: " << header.difficulty
+ << ", size: " << header.block_size << std::endl
<< "major version: " << (unsigned)header.major_version << ", minor version: " << (unsigned)header.minor_version << std::endl
- << "height: " << header.height << ", timestamp: " << header.timestamp << ", difficulty: " << header.difficulty << std::endl
<< "block id: " << header.hash << ", previous block id: " << header.prev_hash << std::endl
<< "difficulty: " << header.difficulty << ", nonce " << header.nonce << ", reward " << cryptonote::print_money(header.reward) << std::endl;
first = false;
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index c2ff63fc7..558031f52 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -143,6 +143,7 @@ namespace cryptonote
res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count();
res.testnet = m_testnet;
res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1);
+ res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit();
res.status = CORE_RPC_STATUS_OK;
return true;
}
@@ -299,6 +300,8 @@ namespace cryptonote
outkey.key = epee::string_tools::pod_to_hex(i.key);
outkey.mask = epee::string_tools::pod_to_hex(i.mask);
outkey.unlocked = i.unlocked;
+ outkey.height = i.height;
+ outkey.txid = epee::string_tools::pod_to_hex(i.txid);
}
res.status = CORE_RPC_STATUS_OK;
@@ -891,6 +894,7 @@ namespace cryptonote
response.hash = string_tools::pod_to_hex(hash);
response.difficulty = m_core.get_blockchain_storage().block_difficulty(height);
response.reward = get_block_reward(blk);
+ response.block_size = m_core.get_blockchain_storage().get_db().get_block_size(height);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -1161,6 +1165,7 @@ namespace cryptonote
res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count();
res.testnet = m_testnet;
res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1);
+ res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit();
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 b09cad235..a4edc7538 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -49,7 +49,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 1
-#define CORE_RPC_VERSION_MINOR 0
+#define CORE_RPC_VERSION_MINOR 4
#define CORE_RPC_VERSION (((CORE_RPC_VERSION_MAJOR)<<16)|(CORE_RPC_VERSION_MINOR))
struct COMMAND_RPC_GET_HEIGHT
@@ -329,11 +329,15 @@ namespace cryptonote
crypto::public_key key;
rct::key mask;
bool unlocked;
+ uint64_t height;
+ crypto::hash txid;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_VAL_POD_AS_BLOB(key)
KV_SERIALIZE_VAL_POD_AS_BLOB(mask)
KV_SERIALIZE(unlocked)
+ KV_SERIALIZE(height)
+ KV_SERIALIZE_VAL_POD_AS_BLOB(txid)
END_KV_SERIALIZE_MAP()
};
@@ -365,11 +369,15 @@ namespace cryptonote
std::string key;
std::string mask;
bool unlocked;
+ uint64_t height;
+ std::string txid;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(key)
KV_SERIALIZE(mask)
KV_SERIALIZE(unlocked)
+ KV_SERIALIZE(height)
+ KV_SERIALIZE(txid)
END_KV_SERIALIZE_MAP()
};
@@ -512,6 +520,7 @@ namespace cryptonote
bool testnet;
std::string top_block_hash;
uint64_t cumulative_difficulty;
+ uint64_t block_size_limit;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
@@ -529,6 +538,7 @@ namespace cryptonote
KV_SERIALIZE(testnet)
KV_SERIALIZE(top_block_hash)
KV_SERIALIZE(cumulative_difficulty)
+ KV_SERIALIZE(block_size_limit)
END_KV_SERIALIZE_MAP()
};
};
@@ -693,6 +703,7 @@ namespace cryptonote
std::string hash;
difficulty_type difficulty;
uint64_t reward;
+ uint64_t block_size;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(major_version)
@@ -706,6 +717,7 @@ namespace cryptonote
KV_SERIALIZE(hash)
KV_SERIALIZE(difficulty)
KV_SERIALIZE(reward)
+ KV_SERIALIZE(block_size)
END_KV_SERIALIZE_MAP()
};
@@ -919,7 +931,7 @@ namespace cryptonote
KV_SERIALIZE(last_failed_id_hash)
KV_SERIALIZE(receive_time)
KV_SERIALIZE(relayed)
- KV_SERIALIZE(last_failed_id_hash)
+ KV_SERIALIZE(last_relayed_time)
END_KV_SERIALIZE_MAP()
};
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index d03f3e7be..54c717503 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -46,6 +46,7 @@
#include "common/i18n.h"
#include "common/command_line.h"
#include "common/util.h"
+#include "common/dns_utils.h"
#include "p2p/net_node.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "simplewallet.h"
@@ -370,6 +371,17 @@ bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string>
return true;
}
+bool simple_wallet::set_print_ring_members(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ m_wallet->print_ring_members(is_it_true(args[1]));
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ }
+ return true;
+}
+
bool simple_wallet::set_store_tx_info(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if (m_wallet->watch_only())
@@ -563,16 +575,18 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>"));
m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address"));
m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID"));
+ m_cmd_binder.set_handler("address_book", boost::bind(&simple_wallet::address_book, this, _1), tr("address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)] - Print all entries in the address book, optionally adding/deleting an entry to/from it"));
m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), tr("Save wallet data"));
m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), tr("Save a watch-only keys file"));
m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), tr("Display private view key"));
m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Display private spend key"));
m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Display Electrum-style mnemonic seed"));
- m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-mixin <n> - set default mixin (default is 4); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [1|2|3] - normal/elevated/priority fee; confirm-missing-payment-id <1|0>"));
+ m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-mixin <n> - set default mixin (default is 4); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [1|2|3] - normal/elevated/priority fee; confirm-missing-payment-id <1|0>"));
m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs"));
m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>"));
m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>"));
m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range"));
+ m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [<min_amount> <max_amount>] - Show unspent outputs within an optional amount range)"));
m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch"));
m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("Set an arbitrary string note for a txid"));
m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), tr("Get a string note for a txid"));
@@ -583,6 +597,7 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("import_key_images", boost::bind(&simple_wallet::import_key_images, this, _1), tr("Import signed key images list and verify their spent status"));
m_cmd_binder.set_handler("export_outputs", boost::bind(&simple_wallet::export_outputs, this, _1), tr("Export a set of outputs owned by this wallet"));
m_cmd_binder.set_handler("import_outputs", boost::bind(&simple_wallet::import_outputs, this, _1), tr("Import set of outputs owned by this wallet"));
+ m_cmd_binder.set_handler("show_transfer", boost::bind(&simple_wallet::show_transfer, this, _1), tr("Show information about a transfer to/from this address"));
m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help"));
}
//----------------------------------------------------------------------------------------------------
@@ -592,6 +607,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
{
success_msg_writer() << "seed = " << m_wallet->get_seed_language();
success_msg_writer() << "always-confirm-transfers = " << m_wallet->always_confirm_transfers();
+ success_msg_writer() << "print-ring-members = " << m_wallet->print_ring_members();
success_msg_writer() << "store-tx-info = " << m_wallet->store_tx_info();
success_msg_writer() << "default-mixin = " << m_wallet->default_mixin();
success_msg_writer() << "auto-refresh = " << m_wallet->auto_refresh();
@@ -611,9 +627,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
}
else if (args[1] == "language")
{
- std::vector<std::string> local_args = args;
- local_args.erase(local_args.begin(), local_args.begin()+2);
- seed_set_language(local_args);
+ seed_set_language(args);
return true;
}
}
@@ -626,9 +640,20 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
}
else
{
- std::vector<std::string> local_args = args;
- local_args.erase(local_args.begin(), local_args.begin()+2);
- set_always_confirm_transfers(local_args);
+ set_always_confirm_transfers(args);
+ return true;
+ }
+ }
+ else if (args[0] == "print-ring-members")
+ {
+ if (args.size() <= 1)
+ {
+ fail_msg_writer() << tr("set print-ring-members: needs an argument (0 or 1)");
+ return true;
+ }
+ else
+ {
+ set_print_ring_members(args);
return true;
}
}
@@ -641,9 +666,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
}
else
{
- std::vector<std::string> local_args = args;
- local_args.erase(local_args.begin(), local_args.begin()+2);
- set_store_tx_info(local_args);
+ set_store_tx_info(args);
return true;
}
}
@@ -656,9 +679,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
}
else
{
- std::vector<std::string> local_args = args;
- local_args.erase(local_args.begin(), local_args.begin()+2);
- set_default_mixin(local_args);
+ set_default_mixin(args);
return true;
}
}
@@ -671,9 +692,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
}
else
{
- std::vector<std::string> local_args = args;
- local_args.erase(local_args.begin(), local_args.begin()+2);
- set_auto_refresh(local_args);
+ set_auto_refresh(args);
return true;
}
}
@@ -687,9 +706,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
}
else
{
- std::vector<std::string> local_args = args;
- local_args.erase(local_args.begin(), local_args.begin()+2);
- set_refresh_type(local_args);
+ set_refresh_type(args);
return true;
}
}
@@ -702,9 +719,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
}
else
{
- std::vector<std::string> local_args = args;
- local_args.erase(local_args.begin(), local_args.begin()+2);
- set_default_priority(local_args);
+ set_default_priority(args);
return true;
}
}
@@ -717,9 +732,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
}
else
{
- std::vector<std::string> local_args = args;
- local_args.erase(local_args.begin(), local_args.begin()+2);
- set_confirm_missing_payment_id(local_args);
+ set_confirm_missing_payment_id(args);
return true;
}
}
@@ -1131,10 +1144,12 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_
return true;
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::try_connect_to_daemon(bool silent)
+bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version)
{
- uint32_t version = 0;
- if (!m_wallet->check_connection(&version))
+ uint32_t version_ = 0;
+ if (!version)
+ version = &version_;
+ if (!m_wallet->check_connection(version))
{
if (!silent)
fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " <<
@@ -1142,10 +1157,10 @@ bool simple_wallet::try_connect_to_daemon(bool silent)
"Please make sure daemon is running or restart the wallet with the correct daemon address.");
return false;
}
- if (!m_allow_mismatched_daemon_version && ((version >> 16) != CORE_RPC_VERSION_MAJOR))
+ if (!m_allow_mismatched_daemon_version && ((*version >> 16) != CORE_RPC_VERSION_MAJOR))
{
if (!silent)
- fail_msg_writer() << boost::format(tr("Daemon uses a different RPC major version (%u) than the wallet (%u): %s. Either update one of them, or use --allow-mismatched-daemon-version.")) % (version>>16) % CORE_RPC_VERSION_MAJOR % m_wallet->get_daemon_address();
+ fail_msg_writer() << boost::format(tr("Daemon uses a different RPC major version (%u) than the wallet (%u): %s. Either update one of them, or use --allow-mismatched-daemon-version.")) % (*version>>16) % CORE_RPC_VERSION_MAJOR % m_wallet->get_daemon_address();
return false;
}
return true;
@@ -1877,73 +1892,106 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::get_address_from_str(const std::string &str, cryptonote::account_public_address &address, bool &has_payment_id, crypto::hash8 &payment_id)
+bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr)
{
- if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), str))
+ uint32_t version;
+ if (!try_connect_to_daemon(false, &version))
+ {
+ fail_msg_writer() << tr("failed to connect to the daemon");
+ return false;
+ }
+ // available for RPC version 1.4 or higher
+ if (version < 0x10004)
+ return true;
+ std::string err;
+ uint64_t blockchain_height = get_daemon_blockchain_height(err);
+ if (!err.empty())
+ {
+ fail_msg_writer() << tr("failed to get blockchain height: ") << err;
+ return false;
+ }
+ // for each transaction
+ for (size_t n = 0; n < ptx_vector.size(); ++n)
+ {
+ const cryptonote::transaction& tx = ptx_vector[n].tx;
+ const tools::wallet2::tx_construction_data& construction_data = ptx_vector[n].construction_data;
+ ostr << boost::format(tr("\nTransaction %llu/%llu: txid=%s")) % (n + 1) % ptx_vector.size() % cryptonote::get_transaction_hash(tx);
+ // for each input
+ std::vector<int> spent_key_height(tx.vin.size());
+ std::vector<crypto::hash> spent_key_txid (tx.vin.size());
+ for (size_t i = 0; i < tx.vin.size(); ++i)
{
- // if treating as an address fails, try as url
- bool dnssec_ok = false;
- std::string url = str;
-
- // attempt to get address from dns query
- auto addresses_from_dns = tools::wallet2::addresses_from_url(url, dnssec_ok);
-
- // for now, move on only if one address found
- if (addresses_from_dns.size() == 1)
+ if (tx.vin[i].type() != typeid(cryptonote::txin_to_key))
+ continue;
+ const cryptonote::txin_to_key& in_key = boost::get<cryptonote::txin_to_key>(tx.vin[i]);
+ const cryptonote::tx_source_entry& source = construction_data.sources[i];
+ ostr << boost::format(tr("\nInput %llu/%llu: amount=%s")) % (i + 1) % tx.vin.size() % print_money(source.amount);
+ // convert relative offsets of ring member keys into absolute offsets (indices) associated with the amount
+ std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key.key_offsets);
+ // get block heights from which those ring member keys originated
+ COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req);
+ req.outputs.resize(absolute_offsets.size());
+ for (size_t j = 0; j < absolute_offsets.size(); ++j)
{
- if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), addresses_from_dns[0]))
- {
- // if it was an address, prompt user for confirmation.
- // inform user of DNSSEC validation status as well.
-
- std::string dnssec_str;
- if (dnssec_ok)
- {
- dnssec_str = tr("DNSSEC validation passed");
- }
- else
- {
- dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!");
- }
- std::stringstream prompt;
- prompt << tr("For URL: ") << url
- << ", " << dnssec_str << std::endl
- << tr(" Monero Address = ") << addresses_from_dns[0]
- << std::endl
- << tr("Is this OK? (Y/n) ")
- ;
-
- // prompt the user for confirmation given the dns query and dnssec status
- std::string confirm_dns_ok = command_line::input_line(prompt.str());
- if (std::cin.eof())
- {
- return false;
- }
- if (!command_line::is_yes(confirm_dns_ok))
- {
- fail_msg_writer() << tr("you have cancelled the transfer request");
- return false;
- }
- }
- else
+ req.outputs[j].amount = in_key.amount;
+ req.outputs[j].index = absolute_offsets[j];
+ }
+ COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res);
+ bool r = net_utils::invoke_http_bin_remote_command2(m_wallet->get_daemon_address() + "/get_outs.bin", req, res, m_http_client);
+ err = interpret_rpc_response(r, res.status);
+ if (!err.empty())
+ {
+ fail_msg_writer() << tr("failed to get output: ") << err;
+ return false;
+ }
+ // make sure that returned block heights are less than blockchain height
+ for (auto& res_out : res.outs)
+ {
+ if (res_out.height >= blockchain_height)
{
- fail_msg_writer() << tr("failed to get a Monero address from: ") << url;
+ fail_msg_writer() << tr("output key's originating block height shouldn't be higher than the blockchain height");
return false;
}
}
- else if (addresses_from_dns.size() > 1)
+ ostr << tr("\nOriginating block heights: ");
+ for (size_t j = 0; j < absolute_offsets.size(); ++j)
+ ostr << tr(j == source.real_output ? " *" : " ") << res.outs[j].height;
+ spent_key_height[i] = res.outs[source.real_output].height;
+ spent_key_txid [i] = res.outs[source.real_output].txid;
+ // visualize the distribution, using the code by moneroexamples onion-monero-viewer
+ const uint64_t resolution = 79;
+ std::string ring_str(resolution, '_');
+ for (size_t j = 0; j < absolute_offsets.size(); ++j)
{
- fail_msg_writer() << tr("not yet supported: Multiple Monero addresses found for given URL: ") << url;
- return false;
+ uint64_t pos = (res.outs[j].height * resolution) / blockchain_height;
+ ring_str[pos] = 'o';
}
- else
+ uint64_t pos = (res.outs[source.real_output].height * resolution) / blockchain_height;
+ ring_str[pos] = '*';
+ ostr << tr("\n|") << ring_str << tr("|\n");
+ }
+ // warn if rings contain keys originating from the same tx or temporally very close block heights
+ bool are_keys_from_same_tx = false;
+ bool are_keys_from_close_height = false;
+ for (size_t i = 0; i < tx.vin.size(); ++i) {
+ for (size_t j = i + 1; j < tx.vin.size(); ++j)
{
- fail_msg_writer() << tr("wrong address: ") << url;
- return false;
+ if (spent_key_txid[i] == spent_key_txid[j])
+ are_keys_from_same_tx = true;
+ if (std::abs(spent_key_height[i] - spent_key_height[j]) < 5)
+ are_keys_from_close_height = true;
}
}
-
- return true;
+ if (are_keys_from_same_tx || are_keys_from_close_height)
+ {
+ ostr
+ << tr("\nWarning: Some input keys being spent are from ")
+ << tr(are_keys_from_same_tx ? "the same transaction" : "blocks that are temporally very close")
+ << tr(", which can break the anonymity of ring signature. Make sure this is intentional!");
+ }
+ ostr << ENDL;
+ }
+ return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_)
@@ -2038,7 +2086,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
cryptonote::tx_destination_entry de;
bool has_payment_id;
crypto::hash8 new_payment_id;
- if (!get_address_from_str(local_args[i], de.addr, has_payment_id, new_payment_id))
+ if (!tools::dns_utils::get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[i]))
return true;
if (has_payment_id)
@@ -2156,7 +2204,12 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
float days = locked_blocks / 720.0f;
prompt << boost::format(tr(".\nThis transaction will unlock on block %llu, in approximately %s days (assuming 2 minutes per block)")) % ((unsigned long long)unlock_block) % days;
}
- prompt << tr(".") << ENDL << tr("Is this okay? (Y/Yes/N/No): ");
+ if (m_wallet->print_ring_members())
+ {
+ if (!print_ring_members(ptx_vector, prompt))
+ return true;
+ }
+ prompt << ENDL << tr("Is this okay? (Y/Yes/N/No): ");
std::string accepted = command_line::input_line(prompt.str());
if (std::cin.eof())
@@ -2526,7 +2579,7 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_)
bool has_payment_id;
crypto::hash8 new_payment_id;
cryptonote::account_public_address address;
- if (!get_address_from_str(local_args[0], address, has_payment_id, new_payment_id))
+ if (!tools::dns_utils::get_account_address_from_str_or_url(address, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[0]))
return true;
if (has_payment_id)
@@ -2584,19 +2637,21 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_)
total_sent += m_wallet->get_transfer_details(i).amount();
}
- std::string prompt_str;
+ std::ostringstream prompt;
+ if (!print_ring_members(ptx_vector, prompt))
+ return true;
if (ptx_vector.size() > 1) {
- prompt_str = (boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) %
+ prompt << boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) %
print_money(total_sent) %
((unsigned long long)ptx_vector.size()) %
- print_money(total_fee)).str();
+ print_money(total_fee);
}
else {
- prompt_str = (boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
+ prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
print_money(total_sent) %
- print_money(total_fee)).str();
+ print_money(total_fee);
}
- std::string accepted = command_line::input_line(prompt_str);
+ std::string accepted = command_line::input_line(prompt.str());
if (std::cin.eof())
return true;
if (!command_line::is_yes(accepted))
@@ -3074,7 +3129,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_)
cryptonote::account_public_address address;
bool has_payment_id;
crypto::hash8 payment_id;
- if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), local_args[2]))
+ if(!tools::dns_utils::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), local_args[2]))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -3382,6 +3437,124 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
return true;
}
//----------------------------------------------------------------------------------------------------
+bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
+{
+ if(!args_.empty() && args_.size() != 2) {
+ fail_msg_writer() << tr("usage: unspent_outputs [<min_amount> <max_amount>]");
+ return true;
+ }
+ uint64_t min_amount = 0;
+ uint64_t max_amount = std::numeric_limits<uint64_t>::max();
+ if (args_.size() == 2)
+ {
+ if (!cryptonote::parse_amount(min_amount, args_[0]) || !cryptonote::parse_amount(max_amount, args_[1]))
+ {
+ fail_msg_writer() << tr("amount is wrong: ") << args_[0] << ' ' << args_[1] <<
+ ", " << tr("expected number from 0 to ") << print_money(std::numeric_limits<uint64_t>::max());
+ return true;
+ }
+ if (min_amount > max_amount)
+ {
+ fail_msg_writer() << tr("<min_amount> should be smaller than <max_amount>");
+ return true;
+ }
+ }
+ tools::wallet2::transfer_container transfers;
+ m_wallet->get_transfers(transfers);
+ if (transfers.empty())
+ {
+ success_msg_writer() << "There is no unspent output in this wallet.";
+ return true;
+ }
+ std::map<uint64_t, tools::wallet2::transfer_container> amount_to_tds;
+ uint64_t min_height = std::numeric_limits<uint64_t>::max();
+ uint64_t max_height = 0;
+ uint64_t found_min_amount = std::numeric_limits<uint64_t>::max();
+ uint64_t found_max_amount = 0;
+ uint64_t count = 0;
+ for (const auto& td : transfers)
+ {
+ uint64_t amount = td.amount();
+ if (td.m_spent || amount < min_amount || amount > max_amount)
+ continue;
+ amount_to_tds[amount].push_back(td);
+ if (min_height > td.m_block_height) min_height = td.m_block_height;
+ if (max_height < td.m_block_height) max_height = td.m_block_height;
+ if (found_min_amount > amount) found_min_amount = amount;
+ if (found_max_amount < amount) found_max_amount = amount;
+ ++count;
+ }
+ for (const auto& amount_tds : amount_to_tds)
+ {
+ auto& tds = amount_tds.second;
+ success_msg_writer() << tr("\nAmount: ") << print_money(amount_tds.first) << tr(", number of keys: ") << tds.size();
+ for (size_t i = 0; i < tds.size(); )
+ {
+ std::ostringstream oss;
+ for (size_t j = 0; j < 8 && i < tds.size(); ++i, ++j)
+ oss << tds[i].m_block_height << tr(" ");
+ success_msg_writer() << oss.str();
+ }
+ }
+ success_msg_writer()
+ << tr("\nMin block height: ") << min_height
+ << tr("\nMax block height: ") << max_height
+ << tr("\nMin amount found: ") << print_money(found_min_amount)
+ << tr("\nMax amount found: ") << print_money(found_max_amount)
+ << tr("\nTotal count: ") << count;
+ const size_t histogram_height = 10;
+ const size_t histogram_width = 50;
+ double bin_size = (max_height - min_height + 1.0) / histogram_width;
+ size_t max_bin_count = 0;
+ std::vector<size_t> histogram(histogram_width, 0);
+ for (const auto& amount_tds : amount_to_tds)
+ {
+ for (auto& td : amount_tds.second)
+ {
+ uint64_t bin_index = (td.m_block_height - min_height + 1) / bin_size;
+ if (bin_index >= histogram_width)
+ bin_index = histogram_width - 1;
+ histogram[bin_index]++;
+ if (max_bin_count < histogram[bin_index])
+ max_bin_count = histogram[bin_index];
+ }
+ }
+ for (size_t x = 0; x < histogram_width; ++x)
+ {
+ double bin_count = histogram[x];
+ if (max_bin_count > histogram_height)
+ bin_count *= histogram_height / (double)max_bin_count;
+ if (histogram[x] > 0 && bin_count < 1.0)
+ bin_count = 1.0;
+ histogram[x] = bin_count;
+ }
+ std::vector<std::string> histogram_line(histogram_height, std::string(histogram_width, ' '));
+ for (size_t y = 0; y < histogram_height; ++y)
+ {
+ for (size_t x = 0; x < histogram_width; ++x)
+ {
+ if (y < histogram[x])
+ histogram_line[y][x] = '*';
+ }
+ }
+ double count_per_star = max_bin_count / (double)histogram_height;
+ if (count_per_star < 1)
+ count_per_star = 1;
+ success_msg_writer()
+ << tr("\nBin size: ") << bin_size
+ << tr("\nOutputs per *: ") << count_per_star;
+ ostringstream histogram_str;
+ histogram_str << tr("count\n ^\n");
+ for (size_t y = histogram_height; y > 0; --y)
+ histogram_str << tr(" |") << histogram_line[y - 1] << tr("|\n");
+ histogram_str
+ << tr(" +") << std::string(histogram_width, '-') << tr("+--> block height\n")
+ << tr(" ^") << std::string(histogram_width - 2, ' ') << tr("^\n")
+ << tr(" ") << min_height << std::string(histogram_width - 8, ' ') << max_height;
+ success_msg_writer() << histogram_str.str();
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_)
{
return refresh_main(0, true);
@@ -3489,6 +3662,86 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg
return true;
}
//----------------------------------------------------------------------------------------------------
+bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ if (args.size() == 0)
+ {
+ }
+ else if (args.size() == 1 || (args[0] != "add" && args[0] != "delete"))
+ {
+ fail_msg_writer() << tr("usage: address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)]");
+ return true;
+ }
+ else if (args[0] == "add")
+ {
+ cryptonote::account_public_address address;
+ bool has_payment_id;
+ crypto::hash8 payment_id8;
+ if(!tools::dns_utils::get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), args[1]))
+ {
+ fail_msg_writer() << tr("failed to parse address");
+ return true;
+ }
+ crypto::hash payment_id = null_hash;
+ size_t description_start = 2;
+ if (has_payment_id)
+ {
+ memcpy(payment_id.data, payment_id8.data, 8);
+ }
+ else if (!has_payment_id && args.size() >= 4 && args[2] == "pid")
+ {
+ if (tools::wallet2::parse_long_payment_id(args[3], payment_id))
+ {
+ description_start += 2;
+ }
+ else if (tools::wallet2::parse_short_payment_id(args[3], payment_id8))
+ {
+ memcpy(payment_id.data, payment_id8.data, 8);
+ description_start += 2;
+ }
+ else
+ {
+ fail_msg_writer() << tr("failed to parse payment ID");
+ return true;
+ }
+ }
+ std::string description;
+ for (size_t i = description_start; i < args.size(); ++i)
+ {
+ if (i > description_start)
+ description += " ";
+ description += args[i];
+ }
+ m_wallet->add_address_book_row(address, payment_id, description);
+ }
+ else
+ {
+ size_t row_id;
+ if(!epee::string_tools::get_xtype_from_string(row_id, args[1]))
+ {
+ fail_msg_writer() << tr("failed to parse index");
+ return true;
+ }
+ m_wallet->delete_address_book_row(row_id);
+ }
+ auto address_book = m_wallet->get_address_book();
+ if (address_book.empty())
+ {
+ success_msg_writer() << tr("Address book is empty.");
+ }
+ else
+ {
+ for (size_t i = 0; i < address_book.size(); ++i) {
+ auto& row = address_book[i];
+ success_msg_writer() << tr("Index: ") << i;
+ success_msg_writer() << tr("Address: ") << get_account_address_as_str(m_wallet->testnet(), row.m_address);
+ success_msg_writer() << tr("Payment ID: ") << row.m_payment_id;
+ success_msg_writer() << tr("Description: ") << row.m_description << "\n";
+ }
+ }
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
bool simple_wallet::set_tx_note(const std::vector<std::string> &args)
{
if (args.size() == 0)
@@ -3614,7 +3867,7 @@ bool simple_wallet::verify(const std::vector<std::string> &args)
cryptonote::account_public_address address;
bool has_payment_id;
crypto::hash8 payment_id;
- if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), address_string))
+ if(!tools::dns_utils::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), address_string))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -3882,6 +4135,128 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
+bool simple_wallet::show_transfer(const std::vector<std::string> &args)
+{
+ if (args.size() != 1)
+ {
+ fail_msg_writer() << tr("usage: show_transfer <txid>");
+ return true;
+ }
+
+ cryptonote::blobdata txid_data;
+ if(!epee::string_tools::parse_hexstr_to_binbuff(args.front(), txid_data))
+ {
+ fail_msg_writer() << tr("failed to parse txid");
+ return false;
+ }
+ crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
+
+ std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
+ m_wallet->get_payments(payments, 0);
+ for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
+ const tools::wallet2::payment_details &pd = i->second;
+ if (pd.m_tx_hash == txid) {
+ std::string payment_id = string_tools::pod_to_hex(i->first);
+ if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
+ payment_id = payment_id.substr(0,16);
+ success_msg_writer() << "Incoming transaction found";
+ success_msg_writer() << "txid: " << txid;
+ success_msg_writer() << "Height: " << pd.m_block_height;
+ success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp);
+ success_msg_writer() << "Amount: " << print_money(pd.m_amount);
+ success_msg_writer() << "Payment ID: " << payment_id;
+ success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid);
+ return true;
+ }
+ }
+
+ std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments_out;
+ m_wallet->get_payments_out(payments_out, 0);
+ for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments_out.begin(); i != payments_out.end(); ++i) {
+ if (i->first == txid)
+ {
+ const tools::wallet2::confirmed_transfer_details &pd = i->second;
+ uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known
+ uint64_t fee = pd.m_amount_in - pd.m_amount_out;
+ std::string dests;
+ for (const auto &d: pd.m_dests) {
+ if (!dests.empty())
+ dests += ", ";
+ dests += get_account_address_as_str(m_wallet->testnet(), d.addr) + ": " + print_money(d.amount);
+ }
+ std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
+ if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
+ payment_id = payment_id.substr(0,16);
+ success_msg_writer() << "Outgoing transaction found";
+ success_msg_writer() << "txid: " << txid;
+ success_msg_writer() << "Height: " << pd.m_block_height;
+ success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp);
+ success_msg_writer() << "Amount: " << print_money(pd.m_amount_in - change - fee);
+ success_msg_writer() << "Payment ID: " << payment_id;
+ success_msg_writer() << "Change: " << print_money(change);
+ success_msg_writer() << "Fee: " << print_money(fee);
+ success_msg_writer() << "Destinations: " << dests;
+ success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid);
+ return true;
+ }
+ }
+
+ try
+ {
+ m_wallet->update_pool_state();
+ std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments;
+ m_wallet->get_unconfirmed_payments(pool_payments);
+ for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
+ const tools::wallet2::payment_details &pd = i->second;
+ if (pd.m_tx_hash == txid)
+ {
+ std::string payment_id = string_tools::pod_to_hex(i->first);
+ if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
+ payment_id = payment_id.substr(0,16);
+ success_msg_writer() << "Unconfirmed incoming transaction found in the txpool";
+ success_msg_writer() << "txid: " << txid;
+ success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp);
+ success_msg_writer() << "Amount: " << print_money(pd.m_amount);
+ success_msg_writer() << "Payment ID: " << payment_id;
+ success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid);
+ return true;
+ }
+ }
+ }
+ catch (...)
+ {
+ fail_msg_writer() << "Failed to get pool state";
+ }
+
+ std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments;
+ m_wallet->get_unconfirmed_payments_out(upayments);
+ for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
+ if (i->first == txid)
+ {
+ const tools::wallet2::unconfirmed_transfer_details &pd = i->second;
+ uint64_t amount = pd.m_amount_in;
+ uint64_t fee = amount - pd.m_amount_out;
+ std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
+ if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
+ payment_id = payment_id.substr(0,16);
+ bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed;
+
+ success_msg_writer() << (is_failed ? "Failed" : "Pending") << " outgoing transaction found";
+ success_msg_writer() << "txid: " << txid;
+ success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp);
+ success_msg_writer() << "Amount: " << print_money(amount - pd.m_change - fee);
+ success_msg_writer() << "Payment ID: " << payment_id;
+ success_msg_writer() << "Change: " << print_money(pd.m_change);
+ success_msg_writer() << "Fee: " << print_money(fee);
+ success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid);
+ return true;
+ }
+ }
+
+ fail_msg_writer() << tr("Transaction ID not found");
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
bool simple_wallet::process_command(const std::vector<std::string> &args)
{
return m_cmd_binder.process_command_vec(args);
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index c3e14a8cc..318d8d7e0 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -106,6 +106,7 @@ namespace cryptonote
*/
bool seed_set_language(const std::vector<std::string> &args = std::vector<std::string>());
bool set_always_confirm_transfers(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_print_ring_members(const std::vector<std::string> &args = std::vector<std::string>());
bool set_store_tx_info(const std::vector<std::string> &args = std::vector<std::string>());
bool set_default_mixin(const std::vector<std::string> &args = std::vector<std::string>());
bool set_auto_refresh(const std::vector<std::string> &args = std::vector<std::string>());
@@ -135,6 +136,7 @@ namespace cryptonote
);
bool print_address(const std::vector<std::string> &args = std::vector<std::string>());
bool print_integrated_address(const std::vector<std::string> &args = std::vector<std::string>());
+ bool address_book(const std::vector<std::string> &args = std::vector<std::string>());
bool save(const std::vector<std::string> &args);
bool save_watch_only(const std::vector<std::string> &args);
bool set_variable(const std::vector<std::string> &args);
@@ -143,6 +145,7 @@ namespace cryptonote
bool get_tx_key(const std::vector<std::string> &args);
bool check_tx_key(const std::vector<std::string> &args);
bool show_transfers(const std::vector<std::string> &args);
+ bool unspent_outputs(const std::vector<std::string> &args);
bool rescan_blockchain(const std::vector<std::string> &args);
bool refresh_main(uint64_t start_height, bool reset = false);
bool set_tx_note(const std::vector<std::string> &args);
@@ -155,14 +158,15 @@ namespace cryptonote
bool import_key_images(const std::vector<std::string> &args);
bool export_outputs(const std::vector<std::string> &args);
bool import_outputs(const std::vector<std::string> &args);
+ bool show_transfer(const std::vector<std::string> &args);
uint64_t get_daemon_blockchain_height(std::string& err);
- bool try_connect_to_daemon(bool silent = false);
+ bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr);
bool ask_wallet_create_if_needed();
bool accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message = std::string());
bool accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs);
bool accept_loaded_tx(const tools::wallet2::signed_tx_set &txs);
- bool get_address_from_str(const std::string &str, cryptonote::account_public_address &address, bool &has_payment_id, crypto::hash8 &payment_id);
+ bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr);
/*!
* \brief Prints the seed with a nice message
diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp
index bbf96c81a..b878491ce 100644
--- a/src/wallet/api/address_book.cpp
+++ b/src/wallet/api/address_book.cpp
@@ -103,6 +103,28 @@ bool AddressBookImpl::deleteRow(std::size_t rowId)
return r;
}
+int AddressBookImpl::lookupPaymentID(const std::string &payment_id) const
+{
+ // turn short ones into long ones for comparison
+ const std::string long_payment_id = payment_id + std::string(64 - payment_id.size(), '0');
+
+ int idx = -1;
+ for (const auto &row: m_rows) {
+ ++idx;
+ // this does short/short and long/long
+ if (payment_id == row->getPaymentId())
+ return idx;
+ // short/long
+ if (long_payment_id == row->getPaymentId())
+ return idx;
+ // one case left: payment_id was long, row's is short
+ const std::string long_row_payment_id = row->getPaymentId() + std::string(64 - row->getPaymentId().size(), '0');
+ if (payment_id == long_row_payment_id)
+ return idx;
+ }
+ return -1;
+}
+
void AddressBookImpl::clearRows() {
for (auto r : m_rows) {
delete r;
diff --git a/src/wallet/api/address_book.h b/src/wallet/api/address_book.h
index 7f30e4387..33d06a078 100644
--- a/src/wallet/api/address_book.h
+++ b/src/wallet/api/address_book.h
@@ -51,6 +51,8 @@ public:
// Error codes. See AddressBook:ErrorCode enum in wallet2_api.h
std::string errorString() const {return m_errorString;}
int errorCode() const {return m_errorCode;}
+
+ int lookupPaymentID(const std::string &payment_id) const;
private:
void clearRows();
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 3a4493ec3..0fb832d65 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -277,6 +277,46 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co
return true;
}
+bool WalletImpl::createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const
+{
+ clearStatus();
+ std::unique_ptr<tools::wallet2> view_wallet(new tools::wallet2(m_wallet->testnet()));
+
+ // Store same refresh height as original wallet
+ view_wallet->set_refresh_from_block_height(m_wallet->get_refresh_from_block_height());
+
+ bool keys_file_exists;
+ bool wallet_file_exists;
+ tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists);
+ LOG_PRINT_L3("wallet_path: " << path << "");
+ LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha
+ << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha);
+
+ // add logic to error out if new wallet requested but named wallet file exists
+ if (keys_file_exists || wallet_file_exists) {
+ m_errorString = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting.";
+ LOG_ERROR(m_errorString);
+ m_status = Status_Error;
+ return false;
+ }
+ // TODO: validate language
+ view_wallet->set_seed_language(language);
+
+ const crypto::secret_key viewkey = m_wallet->get_account().get_keys().m_view_secret_key;
+ const cryptonote::account_public_address address = m_wallet->get_account().get_keys().m_account_address;
+
+ try {
+ view_wallet->generate(path, password, address, viewkey);
+ m_status = Status_Ok;
+ } catch (const std::exception &e) {
+ LOG_ERROR("Error creating view only wallet: " << e.what());
+ m_status = Status_Error;
+ m_errorString = e.what();
+ return false;
+ }
+ return true;
+}
+
bool WalletImpl::open(const std::string &path, const std::string &password)
{
clearStatus();
@@ -415,6 +455,11 @@ std::string WalletImpl::integratedAddress(const std::string &payment_id) const
return m_wallet->get_account().get_public_integrated_address_str(pid, m_wallet->testnet());
}
+std::string WalletImpl::privateViewKey() const
+{
+ return epee::string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key);
+}
+
std::string WalletImpl::path() const
{
return m_wallet->path();
@@ -966,7 +1011,12 @@ bool WalletImpl::trustedDaemon() const
return m_trustedDaemon;
}
-void WalletImpl::clearStatus()
+bool WalletImpl::watchOnly() const
+{
+ return m_wallet->watch_only();
+}
+
+void WalletImpl::clearStatus() const
{
m_status = Status_Ok;
m_errorString.clear();
@@ -1010,9 +1060,6 @@ void WalletImpl::doRefresh()
// Syncing daemon and refreshing wallet simultaneously is very resource intensive.
// Disable refresh if wallet is disconnected or daemon isn't synced.
if (daemonSynced()) {
- // Use fast refresh for new wallets
- if (isNewWallet())
- m_wallet->set_refresh_from_block_height(daemonBlockChainHeight());
m_wallet->refresh();
if (!m_synchronized) {
m_synchronized = true;
@@ -1023,7 +1070,9 @@ void WalletImpl::doRefresh()
if (m_history->count() == 0) {
m_history->refresh();
}
- }
+ } else {
+ LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced");
+ }
} catch (const std::exception &e) {
m_status = Status_Error;
m_errorString = e.what();
@@ -1070,8 +1119,9 @@ bool WalletImpl::isNewWallet() const
// in case wallet created without daemon connection, closed and opened again,
// it's the same case as if it created from scratch, i.e. we need "fast sync"
// with the daemon (pull hashes instead of pull blocks).
- // If wallet cache is rebuilt, creation height stored in .keys is used.
- return !(blockChainHeight() > 1 || m_recoveringFromSeed || m_rebuildWalletCache);
+ // If wallet cache is rebuilt, creation height stored in .keys is used.
+ // Watch only wallet is a copy of an existing wallet.
+ return !(blockChainHeight() > 1 || m_recoveringFromSeed || m_rebuildWalletCache) && !watchOnly();
}
void WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit)
@@ -1079,16 +1129,26 @@ void WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction
m_wallet->init(daemon_address, upper_transaction_size_limit);
// in case new wallet, this will force fast-refresh (pulling hashes instead of blocks)
- if (isNewWallet()) {
+ // If daemon isn't synced a calculated block height will be used instead
+ if (isNewWallet() && daemonSynced()) {
+ LOG_PRINT_L2(__FUNCTION__ << ":New Wallet - fast refresh until " << daemonBlockChainHeight());
m_wallet->set_refresh_from_block_height(daemonBlockChainHeight());
}
+ if (m_rebuildWalletCache)
+ LOG_PRINT_L2(__FUNCTION__ << ": Rebuilding wallet cache, fast refresh until block " << m_wallet->get_refresh_from_block_height());
+
if (Utils::isAddressLocal(daemon_address)) {
this->setTrustedDaemon(true);
}
}
+bool WalletImpl::parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error)
+{
+ return m_wallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error);
+}
+
} // namespace
namespace Bitmonero = Monero;
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index d245a78d0..41b3b22f3 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -53,6 +53,8 @@ public:
~WalletImpl();
bool create(const std::string &path, const std::string &password,
const std::string &language);
+ bool createWatchOnly(const std::string &path, const std::string &password,
+ const std::string &language) const;
bool open(const std::string &path, const std::string &password);
bool recover(const std::string &path, const std::string &seed);
bool close();
@@ -65,6 +67,7 @@ public:
bool setPassword(const std::string &password);
std::string address() const;
std::string integratedAddress(const std::string &payment_id) const;
+ std::string privateViewKey() const;
std::string path() const;
bool store(const std::string &path);
std::string filename() const;
@@ -88,6 +91,7 @@ public:
int autoRefreshInterval() const;
void setRefreshFromBlockHeight(uint64_t refresh_from_block_height);
void setRecoveringFromSeed(bool recoveringFromSeed);
+ bool watchOnly() const;
@@ -109,9 +113,10 @@ public:
virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const;
virtual void startRefresh();
virtual void pauseRefresh();
+ virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error);
private:
- void clearStatus();
+ void clearStatus() const;
void refreshThreadFunc();
void doRefresh();
bool daemonSynced() const;
diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp
index 4ee5ab8df..48faa3183 100644
--- a/src/wallet/api/wallet_manager.cpp
+++ b/src/wallet/api/wallet_manager.cpp
@@ -32,6 +32,7 @@
#include "wallet_manager.h"
#include "wallet.h"
#include "common_defines.h"
+#include "common/dns_utils.h"
#include "net/http_client.h"
#include <boost/filesystem.hpp>
@@ -137,7 +138,7 @@ void WalletManagerImpl::setDaemonAddress(const std::string &address)
m_daemonAddress = address;
}
-bool WalletManagerImpl::connected(uint32_t *version = NULL) const
+bool WalletManagerImpl::connected(uint32_t *version) const
{
epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_VERSION::request> req_t = AUTO_VAL_INIT(req_t);
epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_VERSION::response, std::string> resp_t = AUTO_VAL_INIT(resp_t);
@@ -345,16 +346,83 @@ double WalletManagerImpl::miningHashRate() const
cryptonote::COMMAND_RPC_MINING_STATUS::response mres;
epee::net_utils::http::http_simple_client http_client;
- if (!epee::net_utils::invoke_http_json_remote_command2(m_daemonAddress + "/getinfo", mreq, mres, http_client))
+ if (!epee::net_utils::invoke_http_json_remote_command2(m_daemonAddress + "/mining_status", mreq, mres, http_client))
return 0.0;
if (!mres.active)
return 0.0;
return mres.speed;
}
+void WalletManagerImpl::hardForkInfo(uint8_t &version, uint64_t &earliest_height) const
+{
+ epee::json_rpc::request<cryptonote::COMMAND_RPC_HARD_FORK_INFO::request> req_t = AUTO_VAL_INIT(req_t);
+ epee::json_rpc::response<cryptonote::COMMAND_RPC_HARD_FORK_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t);
+
+ version = 0;
+ earliest_height = 0;
+
+ epee::net_utils::http::http_simple_client http_client;
+ req_t.jsonrpc = "2.0";
+ req_t.id = epee::serialization::storage_entry(0);
+ req_t.method = "hard_fork_info";
+ req_t.params.version = 0;
+ bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemonAddress + "/json_rpc", req_t, resp_t, http_client);
+ if (!r || resp_t.result.status != CORE_RPC_STATUS_OK)
+ return;
+ version = resp_t.result.version;
+ earliest_height = resp_t.result.earliest_height;
+}
+
+uint64_t WalletManagerImpl::blockTarget() const
+{
+ cryptonote::COMMAND_RPC_GET_INFO::request ireq;
+ cryptonote::COMMAND_RPC_GET_INFO::response ires;
+
+ epee::net_utils::http::http_simple_client http_client;
+ if (!epee::net_utils::invoke_http_json_remote_command2(m_daemonAddress + "/getinfo", ireq, ires, http_client))
+ return 0;
+ return ires.target;
+}
+
+bool WalletManagerImpl::isMining() const
+{
+ cryptonote::COMMAND_RPC_MINING_STATUS::request mreq;
+ cryptonote::COMMAND_RPC_MINING_STATUS::response mres;
+
+ epee::net_utils::http::http_simple_client http_client;
+ if (!epee::net_utils::invoke_http_json_remote_command2(m_daemonAddress + "/mining_status", mreq, mres, http_client))
+ return false;
+ return mres.active;
+}
+
+bool WalletManagerImpl::startMining(const std::string &address, uint32_t threads)
+{
+ cryptonote::COMMAND_RPC_START_MINING::request mreq;
+ cryptonote::COMMAND_RPC_START_MINING::response mres;
+
+ mreq.miner_address = address;
+ mreq.threads_count = threads;
+
+ epee::net_utils::http::http_simple_client http_client;
+ if (!epee::net_utils::invoke_http_json_remote_command2(m_daemonAddress + "/start_mining", mreq, mres, http_client))
+ return false;
+ return mres.status == CORE_RPC_STATUS_OK;
+}
+
+bool WalletManagerImpl::stopMining()
+{
+ cryptonote::COMMAND_RPC_STOP_MINING::request mreq;
+ cryptonote::COMMAND_RPC_STOP_MINING::response mres;
+
+ epee::net_utils::http::http_simple_client http_client;
+ if (!epee::net_utils::invoke_http_json_remote_command2(m_daemonAddress + "/stop_mining", mreq, mres, http_client))
+ return false;
+ return mres.status == CORE_RPC_STATUS_OK;
+}
+
std::string WalletManagerImpl::resolveOpenAlias(const std::string &address, bool &dnssec_valid) const
{
- std::vector<std::string> addresses = tools::wallet2::addresses_from_url(address, dnssec_valid);
+ std::vector<std::string> addresses = tools::dns_utils::addresses_from_url(address, dnssec_valid);
if (addresses.empty())
return "";
return addresses.front();
diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h
index 214afc3fa..ca9570254 100644
--- a/src/wallet/api/wallet_manager.h
+++ b/src/wallet/api/wallet_manager.h
@@ -46,12 +46,17 @@ public:
std::vector<std::string> findWallets(const std::string &path);
std::string errorString() const;
void setDaemonAddress(const std::string &address);
- bool connected(uint32_t *version) const;
+ bool connected(uint32_t *version = NULL) const;
bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const;
uint64_t blockchainHeight() const;
uint64_t blockchainTargetHeight() const;
uint64_t networkDifficulty() const;
double miningHashRate() const;
+ void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const;
+ uint64_t blockTarget() const;
+ bool isMining() const;
+ bool startMining(const std::string &address, uint32_t threads = 1);
+ bool stopMining();
std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const;
private:
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 221dd8e0b..b98d6a97a 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -51,7 +51,6 @@ using namespace epee;
#include "cryptonote_protocol/blobdatatype.h"
#include "mnemonics/electrum-words.h"
#include "common/i18n.h"
-#include "common/dns_utils.h"
#include "common/util.h"
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
@@ -77,8 +76,8 @@ using namespace cryptonote;
// arbitrary, used to generate different hashes from the same input
#define CHACHA8_KEY_TAIL 0x8c
-#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\002"
-#define SIGNED_TX_PREFIX "Monero signed tx set\002"
+#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003"
+#define SIGNED_TX_PREFIX "Monero signed tx set\003"
#define RECENT_OUTPUT_RATIO (0.25) // 25% of outputs are from the recent zone
#define RECENT_OUTPUT_ZONE (5 * 86400) // last 5 days are the recent zone
@@ -1613,6 +1612,9 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re
// and then fall through to regular refresh processing
}
+ // If stop() is called during fast refresh we don't need to continue
+ if(!m_run.load(std::memory_order_relaxed))
+ return;
pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices);
// always reset start_height to 0 to force short_chain_ history to be used on
// subsequent pulls in this refresh.
@@ -1632,7 +1634,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re
process_blocks(blocks_start_height, blocks, o_indices, added_blocks);
blocks_fetched += added_blocks;
pull_thread.join();
- if(!added_blocks)
+ if(blocks_start_height == next_blocks_start_height)
break;
// switch to the new blocks from the daemon
@@ -1668,7 +1670,9 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re
try
{
- update_pool_state();
+ // If stop() is called we don't need to check pending transactions
+ if(m_run.load(std::memory_order_relaxed))
+ update_pool_state();
}
catch (...)
{
@@ -1804,6 +1808,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p
value2.SetInt(m_always_confirm_transfers ? 1 :0);
json.AddMember("always_confirm_transfers", value2, json.GetAllocator());
+ value2.SetInt(m_print_ring_members ? 1 :0);
+ json.AddMember("print_ring_members", value2, json.GetAllocator());
+
value2.SetInt(m_store_tx_info ? 1 :0);
json.AddMember("store_tx_info", value2, json.GetAllocator());
@@ -1886,6 +1893,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
is_old_file_format = true;
m_watch_only = false;
m_always_confirm_transfers = false;
+ m_print_ring_members = false;
m_default_mixin = 0;
m_default_priority = 0;
m_auto_refresh = true;
@@ -1916,6 +1924,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
m_watch_only = field_watch_only;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true);
m_always_confirm_transfers = field_always_confirm_transfers;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true);
+ m_print_ring_members = field_print_ring_members;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false, true);
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false, true);
m_store_tx_info = ((field_store_tx_keys != 0) || (field_store_tx_info != 0));
@@ -2139,7 +2149,7 @@ void wallet2::rewrite(const std::string& wallet_name, const std::string& passwor
prepare_file_names(wallet_name);
boost::system::error_code ignored_ec;
THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file);
- bool r = store_keys(m_keys_file, password, false);
+ bool r = store_keys(m_keys_file, password, m_watch_only);
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
}
/*!
@@ -2854,74 +2864,7 @@ std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts(
return retVal;
}
} // anonymous namespace
-
-/**
- * @brief gets a monero address from the TXT record of a DNS entry
- *
- * gets the monero address from the TXT record of the DNS entry associated
- * with <url>. If this lookup fails, or the TXT record does not contain an
- * XMR address in the correct format, returns an empty string. <dnssec_valid>
- * will be set true or false according to whether or not the DNS query passes
- * DNSSEC validation.
- *
- * @param url the url to look up
- * @param dnssec_valid return-by-reference for DNSSEC status of query
- *
- * @return a monero address (as a string) or an empty string
- */
-std::vector<std::string> wallet2::addresses_from_url(const std::string& url, bool& dnssec_valid)
-{
- std::vector<std::string> addresses;
- // get txt records
- bool dnssec_available, dnssec_isvalid;
- std::string oa_addr = tools::DNSResolver::instance().get_dns_format_from_oa_address(url);
- auto records = tools::DNSResolver::instance().get_txt_record(oa_addr, dnssec_available, dnssec_isvalid);
-
- // TODO: update this to allow for conveying that dnssec was not available
- if (dnssec_available && dnssec_isvalid)
- {
- dnssec_valid = true;
- }
- else dnssec_valid = false;
-
- // for each txt record, try to find a monero address in it.
- for (auto& rec : records)
- {
- std::string addr = address_from_txt_record(rec);
- if (addr.size())
- {
- addresses.push_back(addr);
- }
- }
-
- return addresses;
-}
-
//----------------------------------------------------------------------------------------------------
-// TODO: parse the string in a less stupid way, probably with regex
-std::string wallet2::address_from_txt_record(const std::string& s)
-{
- // make sure the txt record has "oa1:xmr" and find it
- auto pos = s.find("oa1:xmr");
-
- // search from there to find "recipient_address="
- pos = s.find("recipient_address=", pos);
-
- pos += 18; // move past "recipient_address="
-
- // find the next semicolon
- auto pos2 = s.find(";", pos);
- if (pos2 != std::string::npos)
- {
- // length of address == 95, we can at least validate that much here
- if (pos2 - pos == 95)
- {
- return s.substr(pos, 95);
- }
- }
- return std::string();
-}
-
crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const
{
std::vector<tx_extra_field> tx_extra_fields;
@@ -3018,14 +2961,19 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri
for (auto &tx: ptx_vector)
txs.txes.push_back(tx.construction_data);
txs.transfers = m_transfers;
- std::string s = obj_to_json_str(txs);
- if (s.empty())
- return false;
- LOG_PRINT_L2("Saving unsigned tx data: " << s);
- // save as binary as there's no implementation of loading a json_archive
- if (!::serialization::dump_binary(txs, s))
+ // save as binary
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << txs;
+ }
+ catch (...)
+ {
return false;
- return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + s);
+ }
+ LOG_PRINT_L2("Saving unsigned tx data: " << oss.str());
+ return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + oss.str());
}
//----------------------------------------------------------------------------------------------------
bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, std::function<bool(const unsigned_tx_set&)> accept_func)
@@ -3050,7 +2998,14 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
return false;
}
unsigned_tx_set exported_txs;
- if (!::serialization::parse_binary(std::string(s.c_str() + magiclen, s.size() - magiclen), exported_txs))
+ s = s.substr(magiclen);
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> exported_txs;
+ }
+ catch (...)
{
LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
return false;
@@ -3123,14 +3078,19 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
signed_txes.key_images[i] = m_transfers[i].m_key_image;
}
- s = obj_to_json_str(signed_txes);
- if (s.empty())
- return false;
- LOG_PRINT_L2("Saving signed tx data: " << s);
- // save as binary as there's no implementation of loading a json_archive
- if (!::serialization::dump_binary(signed_txes, s))
+ // save as binary
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << signed_txes;
+ }
+ catch(...)
+ {
return false;
- return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + s);
+ }
+ LOG_PRINT_L2("Saving signed tx data: " << oss.str());
+ return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + oss.str());
}
//----------------------------------------------------------------------------------------------------
bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func)
@@ -3156,7 +3116,14 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
LOG_PRINT_L0("Bad magic from " << signed_filename);
return false;
}
- if (!::serialization::parse_binary(std::string(s.c_str() + magiclen, s.size() - magiclen), signed_txs))
+ s = s.substr(magiclen);
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> signed_txs;
+ }
+ catch (...)
{
LOG_PRINT_L0("Failed to parse data from " << signed_filename);
return false;
@@ -3521,6 +3488,23 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si
outs.back().reserve(fake_outputs_count + 1);
const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount());
+ // make sure the real outputs we asked for are really included, along
+ // with the correct key and mask: this guards against an active attack
+ // where the node sends dummy data for all outputs, and we then send
+ // the real one, which the node can then tell from the fake outputs,
+ // as it has different data than the dummy data it had sent earlier
+ bool real_out_found = false;
+ for (size_t n = 0; n < requested_outputs_count; ++n)
+ {
+ size_t i = base + n;
+ if (req.outputs[i].index == td.m_global_output_index)
+ if (daemon_resp.outs[i].key == boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key)
+ if (daemon_resp.outs[i].mask == mask)
+ real_out_found = true;
+ }
+ THROW_WALLET_EXCEPTION_IF(!real_out_found, error::wallet_internal_error,
+ "Daemon response did not include the requested real output");
+
// pick real out first (it will be sorted when done)
outs.back().push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask));
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 54e26008b..37609cd2f 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -58,6 +58,8 @@
#include <iostream>
#define WALLET_RCP_CONNECTION_TIMEOUT 200000
+class Serialization_portability_wallet_Test;
+
namespace tools
{
class i_wallet2_callback
@@ -86,6 +88,7 @@ namespace tools
class wallet2
{
+ friend class ::Serialization_portability_wallet_Test;
public:
enum RefreshType {
RefreshFull,
@@ -95,7 +98,7 @@ namespace tools
};
private:
- wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true) {}
+ wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true) {}
public:
static const char* tr(const char* str);// { return i18n_translate(str, "cryptonote::simple_wallet"); }
@@ -116,7 +119,7 @@ namespace tools
//! Uses stdin and stdout. Returns a wallet2 and password for wallet with no file if no errors.
static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm);
- wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_restricted(restricted), is_old_file_format(false) {}
+ wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_restricted(restricted), is_old_file_format(false) {}
struct transfer_details
{
uint64_t m_block_height;
@@ -201,17 +204,6 @@ namespace tools
uint64_t unlock_time;
bool use_rct;
std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change
-
- BEGIN_SERIALIZE_OBJECT()
- FIELD(sources)
- FIELD(change_dts)
- FIELD(splitted_dsts)
- FIELD(selected_transfers)
- FIELD(extra)
- VARINT_FIELD(unlock_time)
- FIELD(use_rct)
- FIELD(dests)
- END_SERIALIZE()
};
typedef std::vector<transfer_details> transfer_container;
@@ -232,39 +224,18 @@ namespace tools
std::vector<cryptonote::tx_destination_entry> dests;
tx_construction_data construction_data;
-
- BEGIN_SERIALIZE_OBJECT()
- FIELD(tx)
- VARINT_FIELD(dust)
- VARINT_FIELD(fee)
- FIELD(dust_added_to_fee)
- FIELD(change_dts)
- FIELD(selected_transfers)
- FIELD(key_images)
- FIELD(tx_key)
- FIELD(dests)
- FIELD(construction_data)
- END_SERIALIZE()
};
struct unsigned_tx_set
{
std::vector<tx_construction_data> txes;
wallet2::transfer_container transfers;
- BEGIN_SERIALIZE_OBJECT()
- FIELD(txes)
- FIELD(transfers)
- END_SERIALIZE()
};
struct signed_tx_set
{
std::vector<pending_tx> ptx;
std::vector<crypto::key_image> key_images;
- BEGIN_SERIALIZE_OBJECT()
- FIELD(ptx)
- FIELD(key_images)
- END_SERIALIZE()
};
struct keys_file_data
@@ -354,6 +325,7 @@ namespace tools
const cryptonote::account_base& get_account()const{return m_account;}
void set_refresh_from_block_height(uint64_t height) {m_refresh_from_block_height = height;}
+ uint64_t get_refresh_from_block_height() const {return m_refresh_from_block_height;}
// upper_transaction_size_limit as defined below is set to
// approximately 125% of the fixed minimum allowable penalty
@@ -503,12 +475,10 @@ namespace tools
static bool parse_short_payment_id(const std::string& payment_id_str, crypto::hash8& payment_id);
static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id);
- static std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid);
-
- static std::string address_from_txt_record(const std::string& s);
-
bool always_confirm_transfers() const { return m_always_confirm_transfers; }
void always_confirm_transfers(bool always) { m_always_confirm_transfers = always; }
+ bool print_ring_members() const { return m_print_ring_members; }
+ void print_ring_members(bool value) { m_print_ring_members = value; }
bool store_tx_info() const { return m_store_tx_info; }
void store_tx_info(bool store) { m_store_tx_info = store; }
uint32_t default_mixin() const { return m_default_mixin; }
@@ -657,6 +627,7 @@ namespace tools
bool is_old_file_format; /*!< Whether the wallet file is of an old file format */
bool m_watch_only; /*!< no spend key */
bool m_always_confirm_transfers;
+ bool m_print_ring_members;
bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */
uint32_t m_default_mixin;
uint32_t m_default_priority;
@@ -672,6 +643,10 @@ BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1)
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 6)
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 3)
BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 16)
+BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0)
+BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0)
+BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 0)
+BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 0)
namespace boost
{
@@ -869,6 +844,48 @@ namespace boost
a & x.m_payment_id;
a & x.m_description;
}
+
+ template <class Archive>
+ inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver)
+ {
+ a & x.txes;
+ a & x.transfers;
+ }
+
+ template <class Archive>
+ inline void serialize(Archive &a, tools::wallet2::signed_tx_set &x, const boost::serialization::version_type ver)
+ {
+ a & x.ptx;
+ a & x.key_images;
+ }
+
+ template <class Archive>
+ inline void serialize(Archive &a, tools::wallet2::tx_construction_data &x, const boost::serialization::version_type ver)
+ {
+ a & x.sources;
+ a & x.change_dts;
+ a & x.splitted_dsts;
+ a & x.selected_transfers;
+ a & x.extra;
+ a & x.unlock_time;
+ a & x.use_rct;
+ a & x.dests;
+ }
+
+ template <class Archive>
+ inline void serialize(Archive &a, tools::wallet2::pending_tx &x, const boost::serialization::version_type ver)
+ {
+ a & x.tx;
+ a & x.dust;
+ a & x.fee;
+ a & x.dust_added_to_fee;
+ a & x.change_dts;
+ a & x.selected_transfers;
+ a & x.key_images;
+ a & x.tx_key;
+ a & x.dests;
+ a & x.construction_data;
+ }
}
}
diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h
index 2e1d95b58..d4c0388a6 100644
--- a/src/wallet/wallet2_api.h
+++ b/src/wallet/wallet2_api.h
@@ -175,6 +175,7 @@ struct AddressBook
virtual void refresh() = 0;
virtual std::string errorString() const = 0;
virtual int errorCode() const = 0;
+ virtual int lookupPaymentID(const std::string &payment_id) const = 0;
};
struct WalletListener
@@ -255,6 +256,12 @@ struct Wallet
*/
virtual std::string integratedAddress(const std::string &payment_id) const = 0;
+ /*!
+ * \brief privateViewKey - returns private view key
+ * \return - private view key
+ */
+ virtual std::string privateViewKey() const = 0;
+
/*!
* \brief store - stores wallet to file.
* \param path - main filename to store wallet to. additionally stores address file and keys file.
@@ -294,6 +301,15 @@ struct Wallet
virtual void initAsync(const std::string &daemon_address, uint64_t upper_transaction_size_limit) = 0;
/*!
+ * \brief createWatchOnly - Creates a watch only wallet
+ * \param path - where to store the wallet
+ * \param password
+ * \param language
+ * \return - true if created successfully
+ */
+ virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
+
+ /*!
* \brief setRefreshFromBlockHeight - start refresh from block height on recover
*
* \param refresh_from_block_height - blockchain start height
@@ -323,6 +339,12 @@ struct Wallet
virtual uint64_t balance() const = 0;
virtual uint64_t unlockedBalance() const = 0;
+ /**
+ * @brief watchOnly - checks if wallet is watch only
+ * @return - true if watch only
+ */
+ virtual bool watchOnly() const = 0;
+
/**
* @brief blockChainHeight - returns current blockchain height
* @return
@@ -468,6 +490,8 @@ struct Wallet
* \return true if the signature verified, false otherwise
*/
virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0;
+
+ virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0;
};
/**
@@ -562,6 +586,21 @@ struct WalletManager
//! returns current mining hash rate (0 if not mining)
virtual double miningHashRate() const = 0;
+ //! returns current hard fork info
+ virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
+
+ //! returns current block target
+ virtual uint64_t blockTarget() const = 0;
+
+ //! returns true iff mining
+ virtual bool isMining() const = 0;
+
+ //! starts mining with the set number of threads
+ virtual bool startMining(const std::string &address, uint32_t threads = 1) = 0;
+
+ //! stops mining
+ virtual bool stopMining() = 0;
+
//! resolves an OpenAlias address to a monero address
virtual std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const = 0;
};
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 2a259029d..d61b11f8a 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -125,7 +125,7 @@ namespace tools
}
}
- epee::net_utils::http::http_auth::login login{};
+ epee::net_utils::http::login login{};
const bool disable_auth = command_line::get_arg(vm, arg_disable_rpc_login);
const std::string user_pass = command_line::get_arg(vm, arg_rpc_login);
@@ -201,6 +201,73 @@ namespace tools
);
}
//------------------------------------------------------------------------------------------------------------------------------
+ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd)
+ {
+ entry.txid = string_tools::pod_to_hex(pd.m_tx_hash);
+ entry.payment_id = string_tools::pod_to_hex(payment_id);
+ if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
+ entry.payment_id = entry.payment_id.substr(0,16);
+ entry.height = pd.m_block_height;
+ entry.timestamp = pd.m_timestamp;
+ entry.amount = pd.m_amount;
+ entry.fee = 0; // TODO
+ entry.note = m_wallet.get_tx_note(pd.m_tx_hash);
+ entry.type = "in";
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd)
+ {
+ entry.txid = string_tools::pod_to_hex(txid);
+ entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id);
+ if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
+ entry.payment_id = entry.payment_id.substr(0,16);
+ entry.height = pd.m_block_height;
+ entry.timestamp = pd.m_timestamp;
+ entry.fee = pd.m_amount_in - pd.m_amount_out;
+ uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known
+ entry.amount = pd.m_amount_in - change - entry.fee;
+ entry.note = m_wallet.get_tx_note(txid);
+
+ for (const auto &d: pd.m_dests) {
+ entry.destinations.push_back(wallet_rpc::transfer_destination());
+ wallet_rpc::transfer_destination &td = entry.destinations.back();
+ td.amount = d.amount;
+ td.address = get_account_address_as_str(m_wallet.testnet(), d.addr);
+ }
+
+ entry.type = "out";
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd)
+ {
+ bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed;
+ entry.txid = string_tools::pod_to_hex(txid);
+ entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id);
+ entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id);
+ if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
+ entry.payment_id = entry.payment_id.substr(0,16);
+ entry.height = 0;
+ entry.timestamp = pd.m_timestamp;
+ entry.fee = pd.m_amount_in - pd.m_amount_out;
+ entry.amount = pd.m_amount_in - pd.m_change - entry.fee;
+ entry.note = m_wallet.get_tx_note(txid);
+ entry.type = is_failed ? "failed" : "pending";
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd)
+ {
+ entry.txid = string_tools::pod_to_hex(pd.m_tx_hash);
+ entry.payment_id = string_tools::pod_to_hex(payment_id);
+ if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
+ entry.payment_id = entry.payment_id.substr(0,16);
+ entry.height = 0;
+ entry.timestamp = pd.m_timestamp;
+ entry.amount = pd.m_amount;
+ entry.fee = 0; // TODO
+ entry.note = m_wallet.get_tx_note(pd.m_tx_hash);
+ entry.type = "pool";
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er)
{
try
@@ -1014,19 +1081,8 @@ namespace tools
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
m_wallet.get_payments(payments, min_height, max_height);
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
- res.in.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry());
- wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.in.back();
- const tools::wallet2::payment_details &pd = i->second;
-
- entry.txid = string_tools::pod_to_hex(pd.m_tx_hash);
- entry.payment_id = string_tools::pod_to_hex(i->first);
- if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
- entry.payment_id = entry.payment_id.substr(0,16);
- entry.height = pd.m_block_height;
- entry.timestamp = pd.m_timestamp;
- entry.amount = pd.m_amount;
- entry.fee = 0; // TODO
- entry.note = m_wallet.get_tx_note(pd.m_tx_hash);
+ res.in.push_back(wallet_rpc::transfer_entry());
+ fill_transfer_entry(res.in.back(), i->second.m_tx_hash, i->first, i->second);
}
}
@@ -1035,27 +1091,8 @@ namespace tools
std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments;
m_wallet.get_payments_out(payments, min_height, max_height);
for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
- res.out.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry());
- wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.out.back();
- const tools::wallet2::confirmed_transfer_details &pd = i->second;
-
- entry.txid = string_tools::pod_to_hex(i->first);
- entry.payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
- if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
- entry.payment_id = entry.payment_id.substr(0,16);
- entry.height = pd.m_block_height;
- entry.timestamp = pd.m_timestamp;
- entry.fee = pd.m_amount_in - pd.m_amount_out;
- uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known
- entry.amount = pd.m_amount_in - change - entry.fee;
- entry.note = m_wallet.get_tx_note(i->first);
-
- for (const auto &d: pd.m_dests) {
- entry.destinations.push_back(wallet_rpc::transfer_destination());
- wallet_rpc::transfer_destination &td = entry.destinations.back();
- td.amount = d.amount;
- td.address = get_account_address_as_str(m_wallet.testnet(), d.addr);
- }
+ res.out.push_back(wallet_rpc::transfer_entry());
+ fill_transfer_entry(res.out.back(), i->first, i->second);
}
}
@@ -1067,20 +1104,9 @@ namespace tools
bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed;
if (!((req.failed && is_failed) || (!is_failed && req.pending)))
continue;
- std::list<wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry> &entries = is_failed ? res.failed : res.pending;
- entries.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry());
- wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = entries.back();
-
- entry.txid = string_tools::pod_to_hex(i->first);
- entry.payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
- entry.payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
- if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
- entry.payment_id = entry.payment_id.substr(0,16);
- entry.height = 0;
- entry.timestamp = pd.m_timestamp;
- entry.fee = pd.m_amount_in - pd.m_amount_out;
- entry.amount = pd.m_amount_in - pd.m_change - entry.fee;
- entry.note = m_wallet.get_tx_note(i->first);
+ std::list<wallet_rpc::transfer_entry> &entries = is_failed ? res.failed : res.pending;
+ entries.push_back(wallet_rpc::transfer_entry());
+ fill_transfer_entry(entries.back(), i->first, i->second);
}
}
@@ -1091,25 +1117,90 @@ namespace tools
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
m_wallet.get_unconfirmed_payments(payments);
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
- res.pool.push_back(wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry());
- wallet_rpc::COMMAND_RPC_GET_TRANSFERS::entry &entry = res.pool.back();
- const tools::wallet2::payment_details &pd = i->second;
-
- entry.txid = string_tools::pod_to_hex(pd.m_tx_hash);
- entry.payment_id = string_tools::pod_to_hex(i->first);
- if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
- entry.payment_id = entry.payment_id.substr(0,16);
- entry.height = 0;
- entry.timestamp = pd.m_timestamp;
- entry.amount = pd.m_amount;
- entry.fee = 0; // TODO
- entry.note = m_wallet.get_tx_note(pd.m_tx_hash);
+ res.pool.push_back(wallet_rpc::transfer_entry());
+ fill_transfer_entry(res.pool.back(), i->first, i->second);
}
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er)
+ {
+ if (m_wallet.restricted())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+
+ crypto::hash txid;
+ cryptonote::blobdata txid_blob;
+ if(!epee::string_tools::parse_hexstr_to_binbuff(req.txid, txid_blob))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID;
+ er.message = "Transaction ID has invalid format";
+ return false;
+ }
+
+ if(sizeof(txid) == txid_blob.size())
+ {
+ txid = *reinterpret_cast<const crypto::hash*>(txid_blob.data());
+ }
+ else
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID;
+ er.message = "Transaction ID has invalid size: " + req.txid;
+ return false;
+ }
+
+ std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
+ m_wallet.get_payments(payments, 0);
+ for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
+ if (i->first == txid)
+ {
+ fill_transfer_entry(res.transfer, i->second.m_tx_hash, i->first, i->second);
+ return true;
+ }
+ }
+
+ std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments_out;
+ m_wallet.get_payments_out(payments_out, 0);
+ for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments_out.begin(); i != payments_out.end(); ++i) {
+ if (i->first == txid)
+ {
+ fill_transfer_entry(res.transfer, i->first, i->second);
+ return true;
+ }
+ }
+
+ std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments;
+ m_wallet.get_unconfirmed_payments_out(upayments);
+ for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
+ if (i->first == txid)
+ {
+ fill_transfer_entry(res.transfer, i->first, i->second);
+ return true;
+ }
+ }
+
+ m_wallet.update_pool_state();
+
+ std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments;
+ m_wallet.get_unconfirmed_payments(pool_payments);
+ for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
+ if (i->second.m_tx_hash == txid)
+ {
+ fill_transfer_entry(res.transfer, i->first, i->second);
+ return true;
+ }
+ }
+
+ er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID;
+ er.message = "Transaction not found.";
+ return false;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er)
{
try
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index 92deec043..4ff1b267f 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -78,6 +78,7 @@ namespace tools
MAP_JON_RPC_WE("set_tx_notes", on_set_tx_notes, wallet_rpc::COMMAND_RPC_SET_TX_NOTES)
MAP_JON_RPC_WE("get_tx_notes", on_get_tx_notes, wallet_rpc::COMMAND_RPC_GET_TX_NOTES)
MAP_JON_RPC_WE("get_transfers", on_get_transfers, wallet_rpc::COMMAND_RPC_GET_TRANSFERS)
+ MAP_JON_RPC_WE("get_transfer_by_txid", on_get_transfer_by_txid, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID)
MAP_JON_RPC_WE("sign", on_sign, wallet_rpc::COMMAND_RPC_SIGN)
MAP_JON_RPC_WE("verify", on_verify, wallet_rpc::COMMAND_RPC_VERIFY)
MAP_JON_RPC_WE("export_key_images", on_export_key_images, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES)
@@ -107,6 +108,7 @@ namespace tools
bool on_set_tx_notes(const wallet_rpc::COMMAND_RPC_SET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_SET_TX_NOTES::response& res, epee::json_rpc::error& er);
bool on_get_tx_notes(const wallet_rpc::COMMAND_RPC_GET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_GET_TX_NOTES::response& res, epee::json_rpc::error& er);
bool on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er);
+ bool on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er);
bool on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er);
bool on_verify(const wallet_rpc::COMMAND_RPC_VERIFY::request& req, wallet_rpc::COMMAND_RPC_VERIFY::response& res, epee::json_rpc::error& er);
bool on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er);
@@ -117,6 +119,12 @@ namespace tools
//json rpc v2
bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er);
+ // helpers
+ void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd);
+ void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd);
+ void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd);
+ void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd);
+
wallet2& m_wallet;
std::string rpc_login_filename;
std::atomic<bool> m_stop;
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 50b1613f9..ea0fc685f 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -525,6 +525,31 @@ namespace wallet_rpc
};
};
+ struct transfer_entry
+ {
+ std::string txid;
+ std::string payment_id;
+ uint64_t height;
+ uint64_t timestamp;
+ uint64_t amount;
+ uint64_t fee;
+ std::string note;
+ std::list<transfer_destination> destinations;
+ std::string type;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(txid);
+ KV_SERIALIZE(payment_id);
+ KV_SERIALIZE(height);
+ KV_SERIALIZE(timestamp);
+ KV_SERIALIZE(amount);
+ KV_SERIALIZE(fee);
+ KV_SERIALIZE(note);
+ KV_SERIALIZE(destinations);
+ KV_SERIALIZE(type);
+ END_KV_SERIALIZE_MAP()
+ };
+
struct COMMAND_RPC_GET_TRANSFERS
{
struct request
@@ -551,43 +576,41 @@ namespace wallet_rpc
END_KV_SERIALIZE_MAP()
};
- struct entry
+ struct response
+ {
+ std::list<transfer_entry> in;
+ std::list<transfer_entry> out;
+ std::list<transfer_entry> pending;
+ std::list<transfer_entry> failed;
+ std::list<transfer_entry> pool;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(in);
+ KV_SERIALIZE(out);
+ KV_SERIALIZE(pending);
+ KV_SERIALIZE(failed);
+ KV_SERIALIZE(pool);
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_GET_TRANSFER_BY_TXID
+ {
+ struct request
{
std::string txid;
- std::string payment_id;
- uint64_t height;
- uint64_t timestamp;
- uint64_t amount;
- uint64_t fee;
- std::string note;
- std::list<transfer_destination> destinations;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(txid);
- KV_SERIALIZE(payment_id);
- KV_SERIALIZE(height);
- KV_SERIALIZE(timestamp);
- KV_SERIALIZE(amount);
- KV_SERIALIZE(fee);
- KV_SERIALIZE(note);
- KV_SERIALIZE(destinations);
END_KV_SERIALIZE_MAP()
};
struct response
{
- std::list<entry> in;
- std::list<entry> out;
- std::list<entry> pending;
- std::list<entry> failed;
- std::list<entry> pool;
+ transfer_entry transfer;
BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(in);
- KV_SERIALIZE(out);
- KV_SERIALIZE(pending);
- KV_SERIALIZE(failed);
- KV_SERIALIZE(pool);
+ KV_SERIALIZE(transfer);
END_KV_SERIALIZE_MAP()
};
};
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 65eab49d7..60b7a4dba 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -62,6 +62,14 @@ else ()
FOLDER "${folder}")
endif ()
+file(COPY
+ data/wallet_9svHk1.keys
+ data/wallet_9svHk1
+ data/outputs
+ data/unsigned_monero_tx
+ data/signed_monero_tx
+ DESTINATION data)
+
add_subdirectory(core_tests)
add_subdirectory(crypto)
add_subdirectory(functional_tests)
diff --git a/tests/data/outputs b/tests/data/outputs
new file mode 100644
index 000000000..abb351c29
--- /dev/null
+++ b/tests/data/outputs
Binary files differ
diff --git a/tests/data/signed_monero_tx b/tests/data/signed_monero_tx
new file mode 100644
index 000000000..f1aa1ee8a
--- /dev/null
+++ b/tests/data/signed_monero_tx
Binary files differ
diff --git a/tests/data/unsigned_monero_tx b/tests/data/unsigned_monero_tx
new file mode 100644
index 000000000..4e644bc45
--- /dev/null
+++ b/tests/data/unsigned_monero_tx
Binary files differ
diff --git a/tests/data/wallet_9svHk1 b/tests/data/wallet_9svHk1
new file mode 100644
index 000000000..a49b6b50e
--- /dev/null
+++ b/tests/data/wallet_9svHk1
Binary files differ
diff --git a/tests/data/wallet_9svHk1.keys b/tests/data/wallet_9svHk1.keys
new file mode 100644
index 000000000..3159e200b
--- /dev/null
+++ b/tests/data/wallet_9svHk1.keys
Binary files differ
diff --git a/tests/unit_tests/address_from_url.cpp b/tests/unit_tests/address_from_url.cpp
index ff163dab9..ad3aca6b4 100644
--- a/tests/unit_tests/address_from_url.cpp
+++ b/tests/unit_tests/address_from_url.cpp
@@ -31,6 +31,7 @@
#include "gtest/gtest.h"
#include "wallet/wallet2.h"
+#include "common/dns_utils.h"
#include <string>
TEST(AddressFromTXT, Success)
@@ -42,7 +43,7 @@ TEST(AddressFromTXT, Success)
txtr += addr;
txtr += ";";
- std::string res = tools::wallet2::address_from_txt_record(txtr);
+ std::string res = tools::dns_utils::address_from_txt_record(txtr);
EXPECT_STREQ(addr.c_str(), res.c_str());
@@ -52,7 +53,7 @@ TEST(AddressFromTXT, Success)
txtr2 += "more foobar";
- res = tools::wallet2::address_from_txt_record(txtr2);
+ res = tools::dns_utils::address_from_txt_record(txtr2);
EXPECT_STREQ(addr.c_str(), res.c_str());
@@ -61,7 +62,7 @@ TEST(AddressFromTXT, Success)
txtr3 += addr;
txtr3 += "; foobar";
- res = tools::wallet2::address_from_txt_record(txtr3);
+ res = tools::dns_utils::address_from_txt_record(txtr3);
EXPECT_STREQ(addr.c_str(), res.c_str());
}
@@ -70,13 +71,13 @@ TEST(AddressFromTXT, Failure)
{
std::string txtr = "oa1:xmr recipient_address=not a real address";
- std::string res = tools::wallet2::address_from_txt_record(txtr);
+ std::string res = tools::dns_utils::address_from_txt_record(txtr);
ASSERT_STREQ("", res.c_str());
txtr += ";";
- res = tools::wallet2::address_from_txt_record(txtr);
+ res = tools::dns_utils::address_from_txt_record(txtr);
ASSERT_STREQ("", res.c_str());
}
@@ -86,7 +87,7 @@ TEST(AddressFromURL, Success)
bool dnssec_result = false;
- std::vector<std::string> addresses = tools::wallet2::addresses_from_url("donate.getmonero.org", dnssec_result);
+ std::vector<std::string> addresses = tools::dns_utils::addresses_from_url("donate.getmonero.org", dnssec_result);
EXPECT_EQ(1, addresses.size());
if (addresses.size() == 1)
@@ -95,7 +96,7 @@ TEST(AddressFromURL, Success)
}
// OpenAlias address with an @ instead of first .
- addresses = tools::wallet2::addresses_from_url("donate@getmonero.org", dnssec_result);
+ addresses = tools::dns_utils::addresses_from_url("donate@getmonero.org", dnssec_result);
EXPECT_EQ(1, addresses.size());
if (addresses.size() == 1)
{
@@ -107,7 +108,7 @@ TEST(AddressFromURL, Failure)
{
bool dnssec_result = false;
- std::vector<std::string> addresses = tools::wallet2::addresses_from_url("example.invalid", dnssec_result);
+ std::vector<std::string> addresses = tools::dns_utils::addresses_from_url("example.invalid", dnssec_result);
// for a non-existing domain such as "example.invalid", the non-existence is proved with NSEC records
ASSERT_TRUE(dnssec_result);
diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp
index be2885f45..ca72f5e0e 100644
--- a/tests/unit_tests/hardfork.cpp
+++ b/tests/unit_tests/hardfork.cpp
@@ -78,6 +78,7 @@ public:
virtual bool tx_exists(const crypto::hash& h, uint64_t& tx_index) const { return false; }
virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const { return 0; }
virtual transaction get_tx(const crypto::hash& h) const { return transaction(); }
+ virtual bool get_tx(const crypto::hash& h, transaction &tx) const { return false; }
virtual uint64_t get_tx_count() const { return 0; }
virtual std::vector<transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const { return std::vector<transaction>(); }
virtual uint64_t get_tx_block_height(const crypto::hash& h) const { return 0; }
diff --git a/tests/unit_tests/http_auth.cpp b/tests/unit_tests/http_auth.cpp
index 7158850b6..97954642f 100644
--- a/tests/unit_tests/http_auth.cpp
+++ b/tests/unit_tests/http_auth.cpp
@@ -61,6 +61,7 @@
#include "string_tools.h"
namespace {
+namespace http = epee::net_utils::http;
using fields = std::unordered_map<std::string, std::string>;
using auth_responses = std::vector<fields>;
@@ -71,17 +72,28 @@ std::string quoted(std::string str)
return str;
}
-epee::net_utils::http::http_request_info make_request(const fields& args)
+void write_fields(std::string& out, const fields& args)
{
namespace karma = boost::spirit::karma;
-
- std::string out{" DIGEST "};
karma::generate(
std::back_inserter(out),
(karma::string << " = " << karma::string) % " , ",
args);
+}
+
+std::string write_fields(const fields& args)
+{
+ std::string out{};
+ write_fields(out, args);
+ return out;
+}
- epee::net_utils::http::http_request_info request{};
+http::http_request_info make_request(const fields& args)
+{
+ std::string out{" DIGEST "};
+ write_fields(out, args);
+
+ http::http_request_info request{};
request.m_http_method_str = "NOP";
request.m_header_info.m_etc_fields.push_back(
std::make_pair(u8"authorization", std::move(out))
@@ -89,6 +101,21 @@ epee::net_utils::http::http_request_info make_request(const fields& args)
return request;
}
+http::http_response_info make_response(const auth_responses& choices)
+{
+ http::http_response_info response{};
+ for (const auto& choice : choices)
+ {
+ std::string out{" DIGEST "};
+ write_fields(out, choice);
+
+ response.m_additional_fields.push_back(
+ std::make_pair(u8"WWW-authenticate", std::move(out))
+ );
+ }
+ return response;
+}
+
bool has_same_fields(const auth_responses& in)
{
const std::vector<std::string> check{u8"nonce", u8"qop", u8"realm", u8"stale"};
@@ -113,7 +140,7 @@ bool has_same_fields(const auth_responses& in)
return true;
}
-bool is_unauthorized(const epee::net_utils::http::http_response_info& response)
+bool is_unauthorized(const http::http_response_info& response)
{
EXPECT_EQ(401, response.m_response_code);
EXPECT_STREQ(u8"Unauthorized", response.m_response_comment.c_str());
@@ -123,39 +150,44 @@ bool is_unauthorized(const epee::net_utils::http::http_response_info& response)
response.m_mime_tipe == u8"text/html";
}
-
-auth_responses parse_response(const epee::net_utils::http::http_response_info& response)
+fields parse_fields(const std::string& value)
{
namespace qi = boost::spirit::qi;
+ fields out{};
+ const bool rc = qi::parse(
+ value.begin(), value.end(),
+ qi::lit(u8"Digest ") >> ((
+ +qi::ascii::alpha >>
+ qi::lit('=') >> (
+ (qi::lit('"') >> +(qi::ascii::char_ - '"') >> qi::lit('"')) |
+ +(qi::ascii::graph - qi::ascii::char_(u8"()<>@,;:\\\"/[]?={}"))
+ )
+ ) % ','
+ ) >> qi::eoi,
+ out
+ );
+ if (!rc)
+ throw std::runtime_error{"Bad field given in HTTP header"};
+
+ return out;
+}
+
+auth_responses parse_response(const http::http_response_info& response)
+{
auth_responses result{};
const auto end = response.m_additional_fields.end();
- for (auto current = response.m_additional_fields.begin(); current != end; ++current)
+ for (auto current = response.m_additional_fields.begin();; ++current)
{
current = std::find_if(current, end, [] (const std::pair<std::string, std::string>& field) {
- return boost::iequals(u8"www-authenticate", field.first);
+ return boost::equals(u8"WWW-authenticate", field.first);
});
if (current == end)
return result;
- std::unordered_map<std::string, std::string> fields{};
- const bool rc = qi::parse(
- current->second.begin(), current->second.end(),
- qi::lit(u8"Digest ") >> ((
- +qi::ascii::alpha >>
- qi::lit('=') >> (
- (qi::lit('"') >> +(qi::ascii::char_ - '"') >> qi::lit('"')) |
- +(qi::ascii::graph - qi::ascii::char_(u8"()<>@,;:\\\"/[]?={}"))
- )
- ) % ','
- ) >> qi::eoi,
- fields
- );
-
- if (rc)
- result.push_back(std::move(fields));
+ result.push_back(parse_fields(current->second));
}
return result;
}
@@ -175,15 +207,20 @@ std::string md5_hex(const std::string& in)
return epee::string_tools::pod_to_hex(digest);
}
-std::string get_a1(const epee::net_utils::http::http_auth::login& user, const auth_responses& responses)
+std::string get_a1(const http::login& user, const fields& src)
{
- const std::string& realm = responses.at(0).at(u8"realm");
+ const std::string& realm = src.at(u8"realm");
return boost::join(
std::vector<std::string>{user.username, realm, user.password}, u8":"
);
}
-std::string get_a1_sess(const epee::net_utils::http::http_auth::login& user, const std::string& cnonce, const auth_responses& responses)
+std::string get_a1(const http::login& user, const auth_responses& responses)
+{
+ return get_a1(user, responses.at(0));
+}
+
+std::string get_a1_sess(const http::login& user, const std::string& cnonce, const auth_responses& responses)
{
const std::string& nonce = responses.at(0).at(u8"nonce");
return boost::join(
@@ -210,36 +247,36 @@ std::string get_nc(std::uint32_t count)
}
}
-TEST(HTTP_Auth, NotRequired)
+TEST(HTTP_Server_Auth, NotRequired)
{
- epee::net_utils::http::http_auth auth{};
- EXPECT_FALSE(auth.get_response(epee::net_utils::http::http_request_info{}));
+ http::http_server_auth auth{};
+ EXPECT_FALSE(auth.get_response(http::http_request_info{}));
}
-TEST(HTTP_Auth, MissingAuth)
+TEST(HTTP_Server_Auth, MissingAuth)
{
- epee::net_utils::http::http_auth auth{{"foo", "bar"}};
- EXPECT_TRUE(bool(auth.get_response(epee::net_utils::http::http_request_info{})));
+ http::http_server_auth auth{{"foo", "bar"}};
+ EXPECT_TRUE(bool(auth.get_response(http::http_request_info{})));
{
- epee::net_utils::http::http_request_info request{};
+ http::http_request_info request{};
request.m_header_info.m_etc_fields.push_back({"\xFF", "\xFF"});
EXPECT_TRUE(bool(auth.get_response(request)));
}
}
-TEST(HTTP_Auth, BadSyntax)
+TEST(HTTP_Server_Auth, BadSyntax)
{
- epee::net_utils::http::http_auth auth{{"foo", "bar"}};
+ http::http_server_auth auth{{"foo", "bar"}};
EXPECT_TRUE(bool(auth.get_response(make_request({{u8"algorithm", "fo\xFF"}}))));
EXPECT_TRUE(bool(auth.get_response(make_request({{u8"cnonce", "\"000\xFF\""}}))));
EXPECT_TRUE(bool(auth.get_response(make_request({{u8"cnonce \xFF =", "\"000\xFF\""}}))));
EXPECT_TRUE(bool(auth.get_response(make_request({{u8" \xFF cnonce", "\"000\xFF\""}}))));
}
-TEST(HTTP_Auth, MD5)
+TEST(HTTP_Server_Auth, MD5)
{
- epee::net_utils::http::http_auth::login user{"foo", "bar"};
- epee::net_utils::http::http_auth auth{user};
+ http::login user{"foo", "bar"};
+ http::http_server_auth auth{user};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
@@ -283,12 +320,12 @@ TEST(HTTP_Auth, MD5)
EXPECT_STREQ(u8"true", fields2[0].at(u8"stale").c_str());
}
-TEST(HTTP_Auth, MD5_sess)
+TEST(HTTP_Server_Auth, MD5_sess)
{
constexpr const char cnonce[] = "not a good cnonce";
- epee::net_utils::http::http_auth::login user{"foo", "bar"};
- epee::net_utils::http::http_auth auth{user};
+ http::login user{"foo", "bar"};
+ http::http_server_auth auth{user};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
@@ -334,13 +371,13 @@ TEST(HTTP_Auth, MD5_sess)
EXPECT_STREQ(u8"true", fields2[0].at(u8"stale").c_str());
}
-TEST(HTTP_Auth, MD5_auth)
+TEST(HTTP_Server_Auth, MD5_auth)
{
constexpr const char cnonce[] = "not a nonce";
constexpr const char qop[] = "auth";
- epee::net_utils::http::http_auth::login user{"foo", "bar"};
- epee::net_utils::http::http_auth auth{user};
+ http::login user{"foo", "bar"};
+ http::http_server_auth auth{user};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
@@ -402,13 +439,13 @@ TEST(HTTP_Auth, MD5_auth)
EXPECT_STREQ(u8"true", parsed_replay[0].at(u8"stale").c_str());
}
-TEST(HTTP_Auth, MD5_sess_auth)
+TEST(HTTP_Server_Auth, MD5_sess_auth)
{
constexpr const char cnonce[] = "not a nonce";
constexpr const char qop[] = "auth";
- epee::net_utils::http::http_auth::login user{"foo", "bar"};
- epee::net_utils::http::http_auth auth{user};
+ http::login user{"foo", "bar"};
+ http::http_server_auth auth{user};
const auto response = auth.get_response(make_request(fields{}));
ASSERT_TRUE(bool(response));
@@ -469,3 +506,219 @@ TEST(HTTP_Auth, MD5_sess_auth)
EXPECT_NE(nonce, parsed_replay[0].at(u8"nonce"));
EXPECT_STREQ(u8"true", parsed_replay[0].at(u8"stale").c_str());
}
+
+
+TEST(HTTP_Auth, DogFood)
+{
+ const auto add_field = [] (http::http_request_info& request, http::http_client_auth& client)
+ {
+ auto field = client.get_auth_field(request.m_http_method_str, request.m_URI);
+ EXPECT_TRUE(bool(field));
+ if (!field)
+ return false;
+ request.m_header_info.m_etc_fields.push_back(std::move(*field));
+ return true;
+ };
+
+ const http::login user{"some_user", "ultimate password"};
+
+ http::http_server_auth server{user};
+ http::http_client_auth client{user};
+
+ http::http_request_info request{};
+ request.m_http_method_str = "GET";
+ request.m_URI = "/FOO";
+
+ const auto response = server.get_response(request);
+ ASSERT_TRUE(bool(response));
+ EXPECT_TRUE(is_unauthorized(*response));
+
+ EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response));
+ EXPECT_TRUE(add_field(request, client));
+ EXPECT_FALSE(bool(server.get_response(request)));
+
+ for (unsigned i = 0; i < 1000; ++i)
+ {
+ request.m_http_method_str += std::to_string(i);
+ request.m_header_info.m_etc_fields.clear();
+ EXPECT_TRUE(add_field(request, client));
+ EXPECT_FALSE(bool(server.get_response(request)));
+ }
+
+ // resetting counter should be rejected by server
+ request.m_header_info.m_etc_fields.clear();
+ client = http::http_client_auth{user};
+ EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response));
+ EXPECT_TRUE(add_field(request, client));
+
+ const auto response2 = server.get_response(request);
+ ASSERT_TRUE(bool(response2));
+ EXPECT_TRUE(is_unauthorized(*response2));
+
+ const auth_responses parsed1 = parse_response(*response);
+ const auth_responses parsed2 = parse_response(*response2);
+ ASSERT_LE(1u, parsed1.size());
+ ASSERT_LE(1u, parsed2.size());
+ EXPECT_NE(parsed1[0].at(u8"nonce"), parsed2[0].at(u8"nonce"));
+
+ // with stale=true client should reset
+ request.m_header_info.m_etc_fields.clear();
+ EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response2));
+ EXPECT_TRUE(add_field(request, client));
+ EXPECT_FALSE(bool(server.get_response(request)));
+
+ // client should give up if stale=false
+ EXPECT_EQ(http::http_client_auth::kBadPassword, client.handle_401(*response));
+}
+
+TEST(HTTP_Client_Auth, Unavailable)
+{
+ http::http_client_auth auth{};
+ EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(http::http_response_info{}));
+ EXPECT_FALSE(bool(auth.get_auth_field("GET", "/file")));
+}
+
+TEST(HTTP_Client_Auth, MissingAuthenticate)
+{
+ http::http_client_auth auth{{"foo", "bar"}};
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(http::http_response_info{}));
+ EXPECT_FALSE(bool(auth.get_auth_field("POST", "/\xFFname")));
+ {
+ http::http_response_info response{};
+ response.m_additional_fields.push_back({"\xFF", "\xFF"});
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(response));
+ }
+ EXPECT_FALSE(bool(auth.get_auth_field("DELETE", "/file/does/not/exist")));
+}
+
+TEST(HTTP_Client_Auth, BadSyntax)
+{
+ http::http_client_auth auth{{"foo", "bar"}};
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"realm", "fo\xFF"}}})));
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"domain", "fo\xFF"}}})));
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"nonce", "fo\xFF"}}})));
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8"nonce \xFF =", "fo\xFF"}}})));
+ EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{u8" \xFF nonce", "fo\xFF"}}})));
+}
+
+TEST(HTTP_Client_Auth, MD5)
+{
+ constexpr char method[] = "NOP";
+ constexpr char nonce[] = "some crazy nonce";
+ constexpr char realm[] = "the only realm";
+ constexpr char uri[] = "/some_file";
+
+ const http::login user{"foo", "bar"};
+ http::http_client_auth auth{user};
+
+ auto response = make_response({
+ {
+ {u8"domain", quoted("ignored")},
+ {u8"nonce", quoted(nonce)},
+ {u8"REALM", quoted(realm)}
+ },
+ {
+ {u8"algorithm", "null"},
+ {u8"domain", quoted("ignored")},
+ {u8"nonce", quoted(std::string{"e"} + nonce)},
+ {u8"realm", quoted(std::string{"e"} + realm)}
+ },
+ });
+
+ EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
+ const auto auth_field = auth.get_auth_field(method, uri);
+ ASSERT_TRUE(bool(auth_field));
+
+ const auto parsed = parse_fields(auth_field->second);
+ EXPECT_STREQ(u8"Authorization", auth_field->first.c_str());
+ EXPECT_EQ(parsed.end(), parsed.find(u8"opaque"));
+ EXPECT_EQ(parsed.end(), parsed.find(u8"qop"));
+ EXPECT_EQ(parsed.end(), parsed.find(u8"nc"));
+ EXPECT_STREQ(u8"MD5", parsed.at(u8"algorithm").c_str());
+ EXPECT_STREQ(nonce, parsed.at(u8"nonce").c_str());
+ EXPECT_STREQ(uri, parsed.at(u8"uri").c_str());
+ EXPECT_EQ(user.username, parsed.at(u8"username"));
+ EXPECT_STREQ(realm, parsed.at(u8"realm").c_str());
+
+ const std::string a1 = get_a1(user, parsed);
+ const std::string a2 = get_a2(uri);
+ const std::string auth_code = md5_hex(
+ boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)}, u8":")
+ );
+ EXPECT_TRUE(boost::iequals(auth_code, parsed.at(u8"response")));
+ {
+ const auto auth_field_dup = auth.get_auth_field(method, uri);
+ ASSERT_TRUE(bool(auth_field_dup));
+ EXPECT_EQ(*auth_field, *auth_field_dup);
+ }
+
+
+ EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(response));
+ response.m_additional_fields.front().second.append(u8"," + write_fields({{u8"stale", u8"TRUE"}}));
+ EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
+}
+
+TEST(HTTP_Client_Auth, MD5_auth)
+{
+ constexpr char cnonce[] = "";
+ constexpr char method[] = "NOP";
+ constexpr char nonce[] = "some crazy nonce";
+ constexpr char opaque[] = "this is the opaque";
+ constexpr char qop[] = u8"ignore,auth,ignore";
+ constexpr char realm[] = "the only realm";
+ constexpr char uri[] = "/some_file";
+
+ const http::login user{"foo", "bar"};
+ http::http_client_auth auth{user};
+
+ auto response = make_response({
+ {
+ {u8"algorithm", u8"MD5"},
+ {u8"domain", quoted("ignored")},
+ {u8"nonce", quoted(std::string{"e"} + nonce)},
+ {u8"realm", quoted(std::string{"e"} + realm)},
+ {u8"qop", quoted("some,thing,to,ignore")}
+ },
+ {
+ {u8"algorIthm", quoted(u8"md5")},
+ {u8"domain", quoted("ignored")},
+ {u8"noNce", quoted(nonce)},
+ {u8"opaque", quoted(opaque)},
+ {u8"realm", quoted(realm)},
+ {u8"QoP", quoted(qop)}
+ }
+ });
+
+ EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
+
+ for (unsigned i = 1; i < 1000; ++i)
+ {
+ const std::string nc = get_nc(i);
+
+ const auto auth_field = auth.get_auth_field(method, uri);
+ ASSERT_TRUE(bool(auth_field));
+
+ const auto parsed = parse_fields(auth_field->second);
+ EXPECT_STREQ(u8"Authorization", auth_field->first.c_str());
+ EXPECT_STREQ(u8"MD5", parsed.at(u8"algorithm").c_str());
+ EXPECT_STREQ(nonce, parsed.at(u8"nonce").c_str());
+ EXPECT_STREQ(opaque, parsed.at(u8"opaque").c_str());
+ EXPECT_STREQ(u8"auth", parsed.at(u8"qop").c_str());
+ EXPECT_STREQ(uri, parsed.at(u8"uri").c_str());
+ EXPECT_EQ(user.username, parsed.at(u8"username"));
+ EXPECT_STREQ(realm, parsed.at(u8"realm").c_str());
+ EXPECT_EQ(nc, parsed.at(u8"nc"));
+
+ const std::string a1 = get_a1(user, parsed);
+ const std::string a2 = get_a2(uri);
+ const std::string auth_code = md5_hex(
+ boost::join(std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce, u8"auth", md5_hex(a2)}, u8":")
+ );
+ EXPECT_TRUE(boost::iequals(auth_code, parsed.at(u8"response")));
+ }
+
+ EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(response));
+ response.m_additional_fields.back().second.append(u8"," + write_fields({{u8"stale", u8"trUe"}}));
+ EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
+}
+
diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp
index 70757dfc1..2be7bde8c 100644
--- a/tests/unit_tests/serialization.cpp
+++ b/tests/unit_tests/serialization.cpp
@@ -34,6 +34,7 @@
#include <iostream>
#include <vector>
#include <boost/foreach.hpp>
+#include <boost/archive/portable_binary_iarchive.hpp>
#include "cryptonote_core/cryptonote_basic.h"
#include "cryptonote_core/cryptonote_basic_impl.h"
#include "ringct/rctSigs.h"
@@ -44,6 +45,7 @@
#include "serialization/variant.h"
#include "serialization/vector.h"
#include "serialization/binary_utils.h"
+#include "wallet/wallet2.h"
#include "gtest/gtest.h"
using namespace std;
@@ -649,3 +651,514 @@ TEST(Serialization, serializes_ringct_types)
ASSERT_TRUE(serialization::dump_binary(tx1, blob2));
ASSERT_TRUE(blob == blob2);
}
+
+TEST(Serialization, portability_wallet)
+{
+ const bool testnet = true;
+ const bool restricted = false;
+ tools::wallet2 w(testnet, restricted);
+ string wallet_file = "../data/wallet_9svHk1";
+ string password = "test";
+ bool r = false;
+ try
+ {
+ w.load(wallet_file, password);
+ r = true;
+ }
+ catch (const exception& e)
+ {}
+ ASSERT_TRUE(r);
+ /*
+ fields of tools::wallet2 to be checked:
+ std::vector<crypto::hash> m_blockchain
+ std::vector<transfer_details> m_transfers // TODO
+ cryptonote::account_public_address m_account_public_address
+ std::unordered_map<crypto::key_image, size_t> m_key_images
+ std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs
+ std::unordered_multimap<crypto::hash, payment_details> m_payments
+ std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys
+ std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs
+ std::unordered_map<crypto::hash, std::string> m_tx_notes
+ std::unordered_map<crypto::hash, payment_details> m_unconfirmed_payments
+ std::unordered_map<crypto::public_key, size_t> m_pub_keys
+ std::vector<tools::wallet2::address_book_row> m_address_book
+ */
+ // blockchain
+ ASSERT_TRUE(w.m_blockchain.size() == 1);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_blockchain[0]) == "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b");
+ // transfers (TODO)
+ ASSERT_TRUE(w.m_transfers.size() == 3);
+ // account public address
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_account_public_address.m_view_public_key) == "e47d4b6df6ab7339539148c2a03ad3e2f3434e5ab2046848e1f21369a3937cad");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_account_public_address.m_spend_public_key) == "13daa2af00ad26a372d317195de0bdd716f7a05d33bc4d7aff1664b6ee93c060");
+ // key images
+ ASSERT_TRUE(w.m_key_images.size() == 3);
+ {
+ crypto::key_image ki[3];
+ epee::string_tools::hex_to_pod("c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8", ki[0]);
+ epee::string_tools::hex_to_pod("d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0", ki[1]);
+ epee::string_tools::hex_to_pod("6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76", ki[2]);
+ ASSERT_TRUE(w.m_key_images.find(ki[0])->second == 0);
+ ASSERT_TRUE(w.m_key_images.find(ki[1])->second == 1);
+ ASSERT_TRUE(w.m_key_images.find(ki[2])->second == 2);
+ }
+ // unconfirmed txs
+ ASSERT_TRUE(w.m_unconfirmed_txs.size() == 0);
+ // payments
+ ASSERT_TRUE(w.m_payments.size() == 2);
+ {
+ auto pd0 = w.m_payments.begin();
+ auto pd1 = pd0;
+ ++pd1;
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(pd0->first) == "0000000000000000000000000000000000000000000000000000000000000000");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(pd1->first) == "0000000000000000000000000000000000000000000000000000000000000000");
+ if (epee::string_tools::pod_to_hex(pd0->second.m_tx_hash) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc")
+ swap(pd0, pd1);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(pd0->second.m_tx_hash) == "15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(pd1->second.m_tx_hash) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc");
+ ASSERT_TRUE(pd0->second.m_amount == 13400845012231);
+ ASSERT_TRUE(pd1->second.m_amount == 1200000000000);
+ ASSERT_TRUE(pd0->second.m_block_height == 818424);
+ ASSERT_TRUE(pd1->second.m_block_height == 818522);
+ ASSERT_TRUE(pd0->second.m_unlock_time == 818484);
+ ASSERT_TRUE(pd1->second.m_unlock_time == 0);
+ ASSERT_TRUE(pd0->second.m_timestamp == 1483263366);
+ ASSERT_TRUE(pd1->second.m_timestamp == 1483272963);
+ }
+ // tx keys
+ ASSERT_TRUE(w.m_tx_keys.size() == 2);
+ {
+ auto tx_key0 = w.m_tx_keys.begin();
+ auto tx_key1 = tx_key0;
+ ++tx_key1;
+ if (epee::string_tools::pod_to_hex(tx_key0->first) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba")
+ swap(tx_key0, tx_key1);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key0->first) == "b9aac8c020ab33859e0c0b6331f46a8780d349e7ac17b067116e2d87bf48daad");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key1->first) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key0->second) == "bf3614c6de1d06c09add5d92a5265d8c76af706f7bc6ac830d6b0d109aa87701");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key1->second) == "e556884246df5a787def6732c6ea38f1e092fa13e5ea98f732b99c07a6332003");
+ }
+ // confirmed txs
+ ASSERT_TRUE(w.m_confirmed_txs.size() == 1);
+ // tx notes
+ ASSERT_TRUE(w.m_tx_notes.size() == 2);
+ {
+ crypto::hash h[2];
+ epee::string_tools::hex_to_pod("15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e", h[0]);
+ epee::string_tools::hex_to_pod("6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba", h[1]);
+ ASSERT_TRUE(w.m_tx_notes.find(h[0])->second == "sample note");
+ ASSERT_TRUE(w.m_tx_notes.find(h[1])->second == "sample note 2");
+ }
+ // unconfirmed payments
+ ASSERT_TRUE(w.m_unconfirmed_payments.size() == 0);
+ // pub keys
+ ASSERT_TRUE(w.m_pub_keys.size() == 3);
+ {
+ crypto::public_key pubkey[3];
+ epee::string_tools::hex_to_pod("33f75f264574cb3a9ea5b24220a5312e183d36dc321c9091dfbb720922a4f7b0", pubkey[0]);
+ epee::string_tools::hex_to_pod("5066ff2ce9861b1d131cf16eeaa01264933a49f28242b97b153e922ec7b4b3cb", pubkey[1]);
+ epee::string_tools::hex_to_pod("0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8", pubkey[2]);
+ ASSERT_TRUE(w.m_pub_keys.find(pubkey[0])->second == 0);
+ ASSERT_TRUE(w.m_pub_keys.find(pubkey[1])->second == 1);
+ ASSERT_TRUE(w.m_pub_keys.find(pubkey[2])->second == 2);
+ }
+ // address book
+ ASSERT_TRUE(w.m_address_book.size() == 1);
+ {
+ auto address_book_row = w.m_address_book.begin();
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_spend_public_key) == "9bc53a6ff7b0831c9470f71b6b972dbe5ad1e8606f72682868b1dda64e119fb3");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_view_public_key) == "49fece1ef97dc0c0f7a5e2106e75e96edd910f7e86b56e1e308cd0cf734df191");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_payment_id) == "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
+ ASSERT_TRUE(address_book_row->m_description == "testnet wallet 9y52S6");
+ }
+}
+
+#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
+TEST(Serialization, portability_outputs)
+{
+ // read file
+ const std::string filename = "../data/outputs";
+ std::string data;
+ bool r = epee::file_io_utils::load_file_to_string(filename, data);
+ ASSERT_TRUE(r);
+ const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC);
+ ASSERT_FALSE(data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen));
+ // decrypt (copied from wallet2::decrypt)
+ auto decrypt = [] (const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) -> string
+ {
+ const size_t prefix_size = sizeof(chacha8_iv) + (authenticated ? sizeof(crypto::signature) : 0);
+ if(ciphertext.size() < prefix_size)
+ return {};
+ crypto::chacha8_key key;
+ crypto::generate_chacha8_key(&skey, sizeof(skey), key);
+ const crypto::chacha8_iv &iv = *(const crypto::chacha8_iv*)&ciphertext[0];
+ std::string plaintext;
+ plaintext.resize(ciphertext.size() - prefix_size);
+ if (authenticated)
+ {
+ crypto::hash hash;
+ crypto::cn_fast_hash(ciphertext.data(), ciphertext.size() - sizeof(signature), hash);
+ crypto::public_key pkey;
+ crypto::secret_key_to_public_key(skey, pkey);
+ const crypto::signature &signature = *(const crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)];
+ if(!crypto::check_signature(hash, pkey, signature))
+ return {};
+ }
+ crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]);
+ return std::move(plaintext);
+ };
+ crypto::secret_key view_secret_key;
+ epee::string_tools::hex_to_pod("339673bb1187e2f73ba7841ab6841c5553f96e9f13f8fe6612e69318db4e9d0a", view_secret_key);
+ bool authenticated = true;
+ data = decrypt(std::string(data, magiclen), view_secret_key, authenticated);
+ ASSERT_FALSE(data.empty());
+ // check public view/spend keys
+ const size_t headerlen = 2 * sizeof(crypto::public_key);
+ ASSERT_FALSE(data.size() < headerlen);
+ const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0];
+ const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)];
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(public_spend_key) == "13daa2af00ad26a372d317195de0bdd716f7a05d33bc4d7aff1664b6ee93c060");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(public_view_key) == "e47d4b6df6ab7339539148c2a03ad3e2f3434e5ab2046848e1f21369a3937cad");
+ r = false;
+ std::vector<tools::wallet2::transfer_details> outputs;
+ try
+ {
+ std::istringstream iss(std::string(data, headerlen));
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> outputs;
+ r = true;
+ }
+ catch (...)
+ {}
+ ASSERT_TRUE(r);
+ /*
+ fields of tools::wallet2::transfer_details to be checked:
+ uint64_t m_block_height
+ cryptonote::transaction_prefix m_tx // TODO
+ crypto::hash m_txid
+ size_t m_internal_output_index
+ uint64_t m_global_output_index
+ bool m_spent
+ uint64_t m_spent_height
+ crypto::key_image m_key_image
+ rct::key m_mask
+ uint64_t m_amount
+ bool m_rct
+ bool m_key_image_known
+ size_t m_pk_index
+ */
+ ASSERT_TRUE(outputs.size() == 3);
+ auto& td0 = outputs[0];
+ auto& td1 = outputs[1];
+ auto& td2 = outputs[2];
+ ASSERT_TRUE(td0.m_block_height == 818424);
+ ASSERT_TRUE(td1.m_block_height == 818522);
+ ASSERT_TRUE(td2.m_block_height == 818522);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_txid) == "15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_txid) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_txid) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba");
+ ASSERT_TRUE(td0.m_internal_output_index == 0);
+ ASSERT_TRUE(td1.m_internal_output_index == 0);
+ ASSERT_TRUE(td2.m_internal_output_index == 1);
+ ASSERT_TRUE(td0.m_global_output_index == 19642);
+ ASSERT_TRUE(td1.m_global_output_index == 19757);
+ ASSERT_TRUE(td2.m_global_output_index == 19760);
+ ASSERT_TRUE (td0.m_spent);
+ ASSERT_FALSE(td1.m_spent);
+ ASSERT_FALSE(td2.m_spent);
+ ASSERT_TRUE(td0.m_spent_height == 0);
+ ASSERT_TRUE(td1.m_spent_height == 0);
+ ASSERT_TRUE(td2.m_spent_height == 0);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_key_image) == "c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_key_image) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_key_image) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_mask) == "0100000000000000000000000000000000000000000000000000000000000000");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_mask) == "d3997a7b27fa199a377643b88cbd3f20f447496746dabe92d288730ecaeda007");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703");
+ ASSERT_TRUE(td0.m_amount == 13400845012231);
+ ASSERT_TRUE(td1.m_amount == 1200000000000);
+ ASSERT_TRUE(td2.m_amount == 11066009260865);
+ ASSERT_TRUE(td0.m_rct);
+ ASSERT_TRUE(td1.m_rct);
+ ASSERT_TRUE(td2.m_rct);
+ ASSERT_TRUE(td0.m_key_image_known);
+ ASSERT_TRUE(td1.m_key_image_known);
+ ASSERT_TRUE(td2.m_key_image_known);
+ ASSERT_TRUE(td0.m_pk_index == 0);
+ ASSERT_TRUE(td1.m_pk_index == 0);
+ ASSERT_TRUE(td2.m_pk_index == 0);
+}
+
+#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003"
+TEST(Serialization, portability_unsigned_tx)
+{
+ const string filename = "../data/unsigned_monero_tx";
+ std::string s;
+ const bool testnet = true;
+ bool r = epee::file_io_utils::load_file_to_string(filename, s);
+ ASSERT_TRUE(r);
+ const size_t magiclen = strlen(UNSIGNED_TX_PREFIX);
+ ASSERT_FALSE(strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen));
+ tools::wallet2::unsigned_tx_set exported_txs;
+ s = s.substr(magiclen);
+ r = false;
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> exported_txs;
+ r = true;
+ }
+ catch (...)
+ {}
+ ASSERT_TRUE(r);
+ /*
+ fields of tools::wallet2::unsigned_tx_set to be checked:
+ std::vector<tx_construction_data> txes
+ std::vector<wallet2::transfer_details> m_transfers
+
+ fields of toolw::wallet2::tx_construction_data to be checked:
+ std::vector<cryptonote::tx_source_entry> sources
+ cryptonote::tx_destination_entry change_dts
+ std::vector<cryptonote::tx_destination_entry> splitted_dsts
+ std::list<size_t> selected_transfers
+ std::vector<uint8_t> extra
+ uint64_t unlock_time
+ bool use_rct
+ std::vector<cryptonote::tx_destination_entry> dests
+
+ fields of cryptonote::tx_source_entry to be checked:
+ std::vector<std::pair<uint64_t, rct::ctkey>> outputs
+ size_t real_output
+ crypto::public_key real_out_tx_key
+ size_t real_output_in_tx_index
+ uint64_t amount
+ bool rct
+ rct::key mask
+
+ fields of cryptonote::tx_destination_entry to be checked:
+ uint64_t amount
+ account_public_address addr
+ */
+ // txes
+ ASSERT_TRUE(exported_txs.txes.size() == 1);
+ auto& tcd = exported_txs.txes[0];
+ // tcd.sources
+ ASSERT_TRUE(tcd.sources.size() == 1);
+ auto& tse = tcd.sources[0];
+ // tcd.sources[0].outputs
+ ASSERT_TRUE(tse.outputs.size() == 5);
+ auto& out0 = tse.outputs[0];
+ auto& out1 = tse.outputs[1];
+ auto& out2 = tse.outputs[2];
+ auto& out3 = tse.outputs[3];
+ auto& out4 = tse.outputs[4];
+ ASSERT_TRUE(out0.first == 6295);
+ ASSERT_TRUE(out1.first == 14302);
+ ASSERT_TRUE(out2.first == 17598);
+ ASSERT_TRUE(out3.first == 18671);
+ ASSERT_TRUE(out4.first == 19760);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(out0.second) == "e7272cb589954ddeedd20de9411ed57265f154d41f33cec9ff69e5d642e09814096490b0ac85308342acf436cc0270d53abef9dc04c6202f2459e879bfd40ce6");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(out1.second) == "c3a9f49d1fe75939cc3feb39871ce0a7366c2879a63faa1a5cf34e65723b120a272ff0c7d84ab8b6ee3528d196450b0e28b3fed276bc2597a2b5b17afb9354ab");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(out2.second) == "176e239c8c39000c2275e2f63ed7d55c55e0843524091522bbd3d3b869044969021fad70fc1244115449d4754829ae7c47346342ee5d52a2cdd47dfc351d0ab0");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(out3.second) == "ef12d7946302fb064f2ba9df1a73d72233ac74664ed3b370580fa3bdc377542ad93f64898bd95851d6efe0d7bf2dbbea9b7c6b3c57e2c807e7b17d55b4622259");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(out4.second) == "0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8525096cbc88d00a841eed66f3cdb6f0a018e6ce9fb9433ed61afba15cbbebd04");
+ // tcd.sources[0].{real_output, real_out_tx_key, real_output_in_tx_index, amount, rct, mask}
+ ASSERT_TRUE(tse.real_output == 4);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.real_out_tx_key) == "4d86c7ba1c285fe4bc1cd7b54ba894fa89fa02fc6b0bbeea67d53251acd14a05");
+ ASSERT_TRUE(tse.real_output_in_tx_index == 1);
+ ASSERT_TRUE(tse.amount == 11066009260865);
+ ASSERT_TRUE(tse.rct);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703");
+ // tcd.change_dts
+ ASSERT_TRUE(tcd.change_dts.amount == 9631208773403);
+ ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, tcd.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk");
+ // tcd.splitted_dsts
+ ASSERT_TRUE(tcd.splitted_dsts.size() == 2);
+ auto& splitted_dst0 = tcd.splitted_dsts[0];
+ auto& splitted_dst1 = tcd.splitted_dsts[1];
+ ASSERT_TRUE(splitted_dst0.amount == 1400000000000);
+ ASSERT_TRUE(splitted_dst1.amount == 9631208773403);
+ ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst0.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA");
+ ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst1.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk");
+ // tcd.selected_transfers
+ ASSERT_TRUE(tcd.selected_transfers.size() == 1);
+ ASSERT_TRUE(tcd.selected_transfers.front() == 2);
+ // tcd.extra
+ ASSERT_TRUE(tcd.extra.size() == 68);
+ string tcd_extra_str = epee::string_tools::buff_to_hex(string(reinterpret_cast<char*>(tcd.extra.data()), tcd.extra.size()));
+ ASSERT_TRUE(tcd_extra_str == "0x2 0x21 0x0 0xf8 0xd 0xbc 0xfc 0xa2 0x2d 0x84 0x1e 0xa0 0x46 0x18 0x7a 0x5b 0x19 0xea 0x4d 0xd1 0xa2 0x8a 0x58 0xa8 0x72 0x9 0xd5 0xdf 0x2 0x30 0x60 0xac 0x9e 0x48 0x84 0x1 0xb2 0xfd 0x5d 0x4e 0x45 0x8b 0xf1 0x28 0xa0 0xc8 0x30 0xd1 0x35 0x4f 0x47 0xb9 0xed 0xc9 0x82 0x8c 0x83 0x37 0x7d 0xb6 0xb5 0xe5 0x3d 0xff 0x64 0xb0 0xde 0x7f ");
+ // tcd.{unlock_time, use_rct}
+ ASSERT_TRUE(tcd.unlock_time == 0);
+ ASSERT_TRUE(tcd.use_rct);
+ // tcd.dests
+ ASSERT_TRUE(tcd.dests.size() == 1);
+ auto& dest = tcd.dests[0];
+ ASSERT_TRUE(dest.amount == 1400000000000);
+ ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, dest.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA");
+ // transfers
+ ASSERT_TRUE(exported_txs.transfers.size() == 3);
+ auto& td0 = exported_txs.transfers[0];
+ auto& td1 = exported_txs.transfers[1];
+ auto& td2 = exported_txs.transfers[2];
+ ASSERT_TRUE(td0.m_block_height == 818424);
+ ASSERT_TRUE(td1.m_block_height == 818522);
+ ASSERT_TRUE(td2.m_block_height == 818522);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_txid) == "15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_txid) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_txid) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba");
+ ASSERT_TRUE(td0.m_internal_output_index == 0);
+ ASSERT_TRUE(td1.m_internal_output_index == 0);
+ ASSERT_TRUE(td2.m_internal_output_index == 1);
+ ASSERT_TRUE(td0.m_global_output_index == 19642);
+ ASSERT_TRUE(td1.m_global_output_index == 19757);
+ ASSERT_TRUE(td2.m_global_output_index == 19760);
+ ASSERT_TRUE (td0.m_spent);
+ ASSERT_FALSE(td1.m_spent);
+ ASSERT_FALSE(td2.m_spent);
+ ASSERT_TRUE(td0.m_spent_height == 0);
+ ASSERT_TRUE(td1.m_spent_height == 0);
+ ASSERT_TRUE(td2.m_spent_height == 0);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_key_image) == "c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_key_image) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_key_image) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_mask) == "0100000000000000000000000000000000000000000000000000000000000000");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_mask) == "d3997a7b27fa199a377643b88cbd3f20f447496746dabe92d288730ecaeda007");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703");
+ ASSERT_TRUE(td0.m_amount == 13400845012231);
+ ASSERT_TRUE(td1.m_amount == 1200000000000);
+ ASSERT_TRUE(td2.m_amount == 11066009260865);
+ ASSERT_TRUE(td0.m_rct);
+ ASSERT_TRUE(td1.m_rct);
+ ASSERT_TRUE(td2.m_rct);
+ ASSERT_TRUE(td0.m_key_image_known);
+ ASSERT_TRUE(td1.m_key_image_known);
+ ASSERT_TRUE(td2.m_key_image_known);
+ ASSERT_TRUE(td0.m_pk_index == 0);
+ ASSERT_TRUE(td1.m_pk_index == 0);
+ ASSERT_TRUE(td2.m_pk_index == 0);
+}
+
+#define SIGNED_TX_PREFIX "Monero signed tx set\003"
+TEST(Serialization, portability_signed_tx)
+{
+ const string filename = "../data/signed_monero_tx";
+ const bool testnet = true;
+ std::string s;
+ bool r = epee::file_io_utils::load_file_to_string(filename, s);
+ ASSERT_TRUE(r);
+ const size_t magiclen = strlen(SIGNED_TX_PREFIX);
+ ASSERT_FALSE(strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen));
+ tools::wallet2::signed_tx_set exported_txs;
+ s = s.substr(magiclen);
+ r = false;
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> exported_txs;
+ r = true;
+ }
+ catch (...)
+ {}
+ ASSERT_TRUE(r);
+ /*
+ fields of tools::wallet2::signed_tx_set to be checked:
+ std::vector<pending_tx> ptx
+ std::vector<crypto::key_image> key_images
+
+ fields of tools::walllet2::pending_tx to be checked:
+ cryptonote::transaction tx // TODO
+ uint64_t dust
+ uint64_t fee
+ bool dust_added_to_fee
+ cryptonote::tx_destination_entry change_dts
+ std::list<size_t> selected_transfers
+ std::string key_images
+ crypto::secret_key tx_key
+ std::vector<cryptonote::tx_destination_entry> dests
+ tx_construction_data construction_data
+ */
+ // ptx
+ ASSERT_TRUE(exported_txs.ptx.size() == 1);
+ auto& ptx = exported_txs.ptx[0];
+ // ptx.{dust, fee, dust_added_to_fee}
+ ASSERT_TRUE (ptx.dust == 0);
+ ASSERT_TRUE (ptx.fee == 34800487462);
+ ASSERT_FALSE(ptx.dust_added_to_fee);
+ // ptx.change.{amount, addr}
+ ASSERT_TRUE(ptx.change_dts.amount == 9631208773403);
+ ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, ptx.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk");
+ // ptx.selected_transfers
+ ASSERT_TRUE(ptx.selected_transfers.size() == 1);
+ ASSERT_TRUE(ptx.selected_transfers.front() == 2);
+ // ptx.{key_images, tx_key}
+ ASSERT_TRUE(ptx.key_images == "<6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76> ");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(ptx.tx_key) == "0100000000000000000000000000000000000000000000000000000000000000");
+ // ptx.dests
+ ASSERT_TRUE(ptx.dests.size() == 1);
+ ASSERT_TRUE(ptx.dests[0].amount == 1400000000000);
+ ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, ptx.dests[0].addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA");
+ // ptx.construction_data
+ auto& tcd = ptx.construction_data;
+ ASSERT_TRUE(tcd.sources.size() == 1);
+ auto& tse = tcd.sources[0];
+ // ptx.construction_data.sources[0].outputs
+ ASSERT_TRUE(tse.outputs.size() == 5);
+ auto& out0 = tse.outputs[0];
+ auto& out1 = tse.outputs[1];
+ auto& out2 = tse.outputs[2];
+ auto& out3 = tse.outputs[3];
+ auto& out4 = tse.outputs[4];
+ ASSERT_TRUE(out0.first == 6295);
+ ASSERT_TRUE(out1.first == 14302);
+ ASSERT_TRUE(out2.first == 17598);
+ ASSERT_TRUE(out3.first == 18671);
+ ASSERT_TRUE(out4.first == 19760);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(out0.second) == "e7272cb589954ddeedd20de9411ed57265f154d41f33cec9ff69e5d642e09814096490b0ac85308342acf436cc0270d53abef9dc04c6202f2459e879bfd40ce6");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(out1.second) == "c3a9f49d1fe75939cc3feb39871ce0a7366c2879a63faa1a5cf34e65723b120a272ff0c7d84ab8b6ee3528d196450b0e28b3fed276bc2597a2b5b17afb9354ab");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(out2.second) == "176e239c8c39000c2275e2f63ed7d55c55e0843524091522bbd3d3b869044969021fad70fc1244115449d4754829ae7c47346342ee5d52a2cdd47dfc351d0ab0");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(out3.second) == "ef12d7946302fb064f2ba9df1a73d72233ac74664ed3b370580fa3bdc377542ad93f64898bd95851d6efe0d7bf2dbbea9b7c6b3c57e2c807e7b17d55b4622259");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(out4.second) == "0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8525096cbc88d00a841eed66f3cdb6f0a018e6ce9fb9433ed61afba15cbbebd04");
+ // ptx.construction_data.sources[0].{real_output, real_out_tx_key, real_output_in_tx_index, amount, rct, mask}
+ ASSERT_TRUE(tse.real_output == 4);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.real_out_tx_key) == "4d86c7ba1c285fe4bc1cd7b54ba894fa89fa02fc6b0bbeea67d53251acd14a05");
+ ASSERT_TRUE(tse.real_output_in_tx_index == 1);
+ ASSERT_TRUE(tse.amount == 11066009260865);
+ ASSERT_TRUE(tse.rct);
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703");
+ // ptx.construction_data.change_dts
+ ASSERT_TRUE(tcd.change_dts.amount == 9631208773403);
+ ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, tcd.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk");
+ // ptx.construction_data.splitted_dsts
+ ASSERT_TRUE(tcd.splitted_dsts.size() == 2);
+ auto& splitted_dst0 = tcd.splitted_dsts[0];
+ auto& splitted_dst1 = tcd.splitted_dsts[1];
+ ASSERT_TRUE(splitted_dst0.amount == 1400000000000);
+ ASSERT_TRUE(splitted_dst1.amount == 9631208773403);
+ ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst0.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA");
+ ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst1.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk");
+ // ptx.construction_data.selected_transfers
+ ASSERT_TRUE(tcd.selected_transfers.size() == 1);
+ ASSERT_TRUE(tcd.selected_transfers.front() == 2);
+ // ptx.construction_data.extra
+ ASSERT_TRUE(tcd.extra.size() == 68);
+ string tcd_extra_str = epee::string_tools::buff_to_hex(string(reinterpret_cast<char*>(tcd.extra.data()), tcd.extra.size()));
+ ASSERT_TRUE(tcd_extra_str == "0x2 0x21 0x0 0xf8 0xd 0xbc 0xfc 0xa2 0x2d 0x84 0x1e 0xa0 0x46 0x18 0x7a 0x5b 0x19 0xea 0x4d 0xd1 0xa2 0x8a 0x58 0xa8 0x72 0x9 0xd5 0xdf 0x2 0x30 0x60 0xac 0x9e 0x48 0x84 0x1 0xb2 0xfd 0x5d 0x4e 0x45 0x8b 0xf1 0x28 0xa0 0xc8 0x30 0xd1 0x35 0x4f 0x47 0xb9 0xed 0xc9 0x82 0x8c 0x83 0x37 0x7d 0xb6 0xb5 0xe5 0x3d 0xff 0x64 0xb0 0xde 0x7f ");
+ // ptx.construction_data.{unlock_time, use_rct}
+ ASSERT_TRUE(tcd.unlock_time == 0);
+ ASSERT_TRUE(tcd.use_rct);
+ // ptx.construction_data.dests
+ ASSERT_TRUE(tcd.dests.size() == 1);
+ auto& dest = tcd.dests[0];
+ ASSERT_TRUE(dest.amount == 1400000000000);
+ ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, dest.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA");
+ // key_images
+ ASSERT_TRUE(exported_txs.key_images.size() == 3);
+ auto& ki0 = exported_txs.key_images[0];
+ auto& ki1 = exported_txs.key_images[1];
+ auto& ki2 = exported_txs.key_images[2];
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(ki0) == "c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(ki1) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0");
+ ASSERT_TRUE(epee::string_tools::pod_to_hex(ki2) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76");
+}