aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt34
-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/db_types.h1
-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/command_line.cpp10
-rw-r--r--src/common/command_line.h1
-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/account.cpp8
-rw-r--r--src/cryptonote_core/blockchain.cpp24
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp22
-rw-r--r--src/cryptonote_core/cryptonote_core.h8
-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.cpp10
-rw-r--r--src/rpc/core_rpc_server.cpp7
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h18
-rw-r--r--src/simplewallet/simplewallet.cpp660
-rw-r--r--src/simplewallet/simplewallet.h9
-rw-r--r--src/wallet/CMakeLists.txt6
-rw-r--r--src/wallet/api/address_book.cpp22
-rw-r--r--src/wallet/api/address_book.h2
-rw-r--r--src/wallet/api/pending_transaction.cpp30
-rw-r--r--src/wallet/api/pending_transaction.h2
-rw-r--r--src/wallet/api/transaction_history.cpp36
-rw-r--r--src/wallet/api/transaction_info.cpp6
-rw-r--r--src/wallet/api/transaction_info.h2
-rw-r--r--src/wallet/api/unsigned_transaction.cpp279
-rw-r--r--src/wallet/api/unsigned_transaction.h76
-rw-r--r--src/wallet/api/wallet.cpp144
-rw-r--r--src/wallet/api/wallet.h11
-rw-r--r--src/wallet/api/wallet_manager.cpp74
-rw-r--r--src/wallet/api/wallet_manager.h7
-rw-r--r--src/wallet/wallet2.cpp314
-rw-r--r--src/wallet/wallet2.h104
-rw-r--r--src/wallet/wallet2_api.h107
-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/libwallet_api_tests/main.cpp10
-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
72 files changed, 3620 insertions, 735 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 87a53c368..eea161bdb 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)
@@ -270,14 +273,19 @@ endif()
add_definitions("-DBLOCKCHAIN_DB=${BLOCKCHAIN_DB}")
-find_package(Libunwind)
# Can't install hook in static build on OSX, because OSX linker does not support --wrap
# On ARM, having libunwind package (with .so's only) installed breaks static link.
-if(LIBUNWIND_FOUND AND NOT (STATIC AND (APPLE OR ARM)))
- set(DEFAULT_STACK_TRACE ON)
-else()
+if(APPLE OR (ARM AND STATIC))
set(DEFAULT_STACK_TRACE OFF)
set(LIBUNWIND_LIBRARIES "")
+else()
+ find_package(Libunwind)
+ if(LIBUNWIND_FOUND)
+ set(DEFAULT_STACK_TRACE ON)
+ else()
+ set(DEFAULT_STACK_TRACE OFF)
+ set(LIBUNWIND_LIBRARIES "")
+ endif()
endif()
option(STACK_TRACE "Install a hook that dumps stack on exception" ${DEFAULT_STACK_TRACE})
@@ -375,6 +383,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 +425,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 +509,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 +602,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 81380b56d..455e0c811 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -1021,6 +1021,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/db_types.h b/src/blockchain_db/db_types.h
index ca4abf219..67afe0405 100644
--- a/src/blockchain_db/db_types.h
+++ b/src/blockchain_db/db_types.h
@@ -34,7 +34,6 @@ namespace cryptonote
const std::unordered_set<std::string> blockchain_db_types =
{ "lmdb"
- , "berkeley"
};
} // namespace cryptonote
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index e7a3f36c0..ca79ab4f8 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -1803,7 +1803,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();
@@ -1822,19 +1822,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 1c5c9e3d8..e7faf8cdc 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/command_line.cpp b/src/common/command_line.cpp
index 28879e098..d95859256 100644
--- a/src/common/command_line.cpp
+++ b/src/common/command_line.cpp
@@ -31,6 +31,8 @@
#include "command_line.h"
#include <boost/algorithm/string/compare.hpp>
#include <boost/algorithm/string/predicate.hpp>
+#include <unordered_set>
+#include "blockchain_db/db_types.h"
#include "common/i18n.h"
#include "cryptonote_config.h"
#include "string_tools.h"
@@ -88,9 +90,10 @@ namespace command_line
, "checkpoints from DNS server will be enforced"
, false
};
+ std::string arg_db_type_description = "Specify database type, available: " + boost::algorithm::join(cryptonote::blockchain_db_types, ", ");
const command_line::arg_descriptor<std::string> arg_db_type = {
"db-type"
- , "Specify database type"
+ , arg_db_type_description.c_str()
, DEFAULT_DB_TYPE
};
const command_line::arg_descriptor<std::string> arg_db_sync_mode = {
@@ -108,11 +111,6 @@ namespace command_line
, "Max number of threads to use when preparing block hashes in groups."
, 4
};
- const command_line::arg_descriptor<uint64_t> arg_db_auto_remove_logs = {
- "db-auto-remove-logs"
- , "For BerkeleyDB only. Remove transactions logs automatically."
- , 1
- };
const command_line::arg_descriptor<uint64_t> arg_show_time_stats = {
"show-time-stats"
, "Show time-stats when processing blocks/txs and disk synchronization."
diff --git a/src/common/command_line.h b/src/common/command_line.h
index 98c115bb7..3f0919e99 100644
--- a/src/common/command_line.h
+++ b/src/common/command_line.h
@@ -217,7 +217,6 @@ namespace command_line
extern const arg_descriptor<std::string> arg_db_sync_mode;
extern const arg_descriptor<uint64_t> arg_fast_block_sync;
extern const arg_descriptor<uint64_t> arg_prep_blocks_threads;
- extern const arg_descriptor<uint64_t> arg_db_auto_remove_logs;
extern const arg_descriptor<uint64_t> arg_show_time_stats;
extern const arg_descriptor<size_t> arg_block_sync_size;
}
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/account.cpp b/src/cryptonote_core/account.cpp
index 89ad4184c..bd703eee2 100644
--- a/src/cryptonote_core/account.cpp
+++ b/src/cryptonote_core/account.cpp
@@ -72,7 +72,7 @@ DISABLE_VS_WARNINGS(4244 4345)
generate_keys(m_keys.m_account_address.m_view_public_key, m_keys.m_view_secret_key, second, two_random ? false : true);
- struct tm timestamp;
+ struct tm timestamp = {0};
timestamp.tm_year = 2014 - 1900; // year 2014
timestamp.tm_mon = 6 - 1; // month june
timestamp.tm_mday = 8; // 8th of june
@@ -83,6 +83,8 @@ DISABLE_VS_WARNINGS(4244 4345)
if (recover)
{
m_creation_timestamp = mktime(&timestamp);
+ if (m_creation_timestamp == (uint64_t)-1) // failure
+ m_creation_timestamp = 0; // lowest value
}
else
{
@@ -97,7 +99,7 @@ DISABLE_VS_WARNINGS(4244 4345)
m_keys.m_spend_secret_key = spendkey;
m_keys.m_view_secret_key = viewkey;
- struct tm timestamp;
+ struct tm timestamp = {0};
timestamp.tm_year = 2014 - 1900; // year 2014
timestamp.tm_mon = 4 - 1; // month april
timestamp.tm_mday = 15; // 15th of april
@@ -106,6 +108,8 @@ DISABLE_VS_WARNINGS(4244 4345)
timestamp.tm_sec = 0;
m_creation_timestamp = mktime(&timestamp);
+ if (m_creation_timestamp == (uint64_t)-1) // failure
+ m_creation_timestamp = 0; // lowest value
}
//-----------------------------------------------------------------
void account_base::create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey)
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index c368d7b24..f0779dad0 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)
{
@@ -2187,7 +2188,10 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh
max_used_block_height = 0;
TIME_MEASURE_FINISH(a);
if(m_show_time_stats)
- LOG_PRINT_L0("HASH: " << "-" << " VIN/VOUT: " << tx.vin.size() << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a);
+ {
+ size_t mixin = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() - 1 : 0;
+ LOG_PRINT_L0("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << mixin << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a);
+ }
return true;
}
#endif
@@ -2197,8 +2201,8 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh
TIME_MEASURE_FINISH(a);
if(m_show_time_stats)
{
- size_t mix = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() : 0;
- LOG_PRINT_L0("HASH: " << get_transaction_hash(tx) << " VIN/MIX/VOUT: " << tx.vin.size() << "/" << mix << "/" << tx.vout.size() << " H: " << max_used_block_height << " ms: " << a + m_fake_scan_time);
+ size_t mixin = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() - 1 : 0;
+ LOG_PRINT_L0("HASH: " << get_transaction_hash(tx) << " I/M/O: " << tx.vin.size() << "/" << mixin << "/" << tx.vout.size() << " H: " << max_used_block_height << " ms: " << a + m_fake_scan_time << " B: " << get_object_blobsize(tx));
}
if (!res)
return false;
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index cd3ed9ab0..c2da7aaea 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -141,7 +141,6 @@ namespace cryptonote
command_line::add_arg(desc, command_line::arg_fast_block_sync);
command_line::add_arg(desc, command_line::arg_db_sync_mode);
command_line::add_arg(desc, command_line::arg_show_time_stats);
- command_line::add_arg(desc, command_line::arg_db_auto_remove_logs);
command_line::add_arg(desc, command_line::arg_block_sync_size);
}
//-----------------------------------------------------------------------------------------------
@@ -250,6 +249,8 @@ namespace cryptonote
//-----------------------------------------------------------------------------------------------
bool core::init(const boost::program_options::variables_map& vm, const cryptonote::test_options *test_options)
{
+ start_time = std::time(nullptr);
+
m_fakechain = test_options != NULL;
bool r = handle_command_line(vm);
@@ -301,18 +302,6 @@ namespace cryptonote
DBS_FAST_MODE = MDB_NORDAHEAD | MDB_NOSYNC;
DBS_FASTEST_MODE = MDB_NORDAHEAD | MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC;
}
- else if (db_type == "berkeley")
- {
-#if defined(BERKELEY_DB)
- db = new BlockchainBDB();
- DBS_FAST_MODE = DB_TXN_WRITE_NOSYNC;
- DBS_FASTEST_MODE = DB_TXN_NOSYNC;
- DBS_SAFE_MODE = DB_TXN_SYNC;
-#else
- LOG_ERROR("BerkeleyDB support disabled.");
- return false;
-#endif
- }
else
{
LOG_ERROR("Attempted to use non-existent database type");
@@ -383,8 +372,6 @@ namespace cryptonote
blocks_per_sync = bps;
}
- bool auto_remove_logs = command_line::get_arg(vm, command_line::arg_db_auto_remove_logs) != 0;
- db->set_auto_remove_logs(auto_remove_logs);
db->open(filename, db_flags);
if(!db->m_open)
return false;
@@ -1013,6 +1000,11 @@ namespace cryptonote
return m_target_blockchain_height;
}
//-----------------------------------------------------------------------------------------------
+ std::time_t core::get_start_time() const
+ {
+ return start_time;
+ }
+ //-----------------------------------------------------------------------------------------------
void core::graceful_exit()
{
raise(SIGTERM);
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index 21f84cdd4..a9e80aeee 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -560,6 +560,12 @@ namespace cryptonote
uint64_t get_target_blockchain_height() const;
/**
+ * @brief gets start_time
+ *
+ */
+ std::time_t get_start_time() const;
+
+ /**
* @brief tells the Blockchain to update its checkpoints
*
* This function will check if enough time has passed since the last
@@ -813,6 +819,8 @@ namespace cryptonote
boost::interprocess::file_lock db_lock; //!< a lock object for a file lock in the db directory
size_t block_sync_size;
+
+ time_t start_time;
};
}
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..447783d76 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -363,7 +363,9 @@ bool t_rpc_command_executor::show_status() {
}
}
- tools::success_msg_writer() << boost::format("Height: %llu/%llu (%.1f%%) on %s, %s, net hash %s, v%u%s, %s, %u+%u connections")
+ std::time_t uptime = std::time(nullptr) - ires.start_time;
+
+ tools::success_msg_writer() << boost::format("Height: %llu/%llu (%.1f%%) on %s, %s, net hash %s, v%u%s, %s, %u+%u connections, uptime %uh %um %us")
% (unsigned long long)ires.height
% (unsigned long long)(ires.target_height >= ires.height ? ires.target_height : ires.height)
% get_sync_percentage(ires)
@@ -374,6 +376,9 @@ bool t_rpc_command_executor::show_status() {
% get_fork_extra_info(hfres.earliest_height, ires.height, ires.target)
% (hfres.state == cryptonote::HardFork::Ready ? "up to date" : hfres.state == cryptonote::HardFork::UpdateNeeded ? "update needed" : "out of date, likely forked")
% (unsigned)ires.outgoing_connections_count % (unsigned)ires.incoming_connections_count
+ % (unsigned int)floor(uptime / 3600.0)
+ % (unsigned int)floor(fmod(uptime, 3600.0) / 60.0)
+ % (unsigned int)fmod(uptime, 60.0)
;
return true;
@@ -473,8 +478,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..ddf892cae 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -143,7 +143,9 @@ 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;
+ res.start_time = (uint64_t)m_core.get_start_time();
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -299,6 +301,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 +895,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,7 +1166,9 @@ 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;
+ res.start_time = (uint64_t)m_core.get_start_time();
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index b09cad235..fa86c08e4 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,8 @@ namespace cryptonote
bool testnet;
std::string top_block_hash;
uint64_t cumulative_difficulty;
+ uint64_t block_size_limit;
+ uint64_t start_time;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
@@ -529,6 +539,8 @@ namespace cryptonote
KV_SERIALIZE(testnet)
KV_SERIALIZE(top_block_hash)
KV_SERIALIZE(cumulative_difficulty)
+ KV_SERIALIZE(block_size_limit)
+ KV_SERIALIZE(start_time)
END_KV_SERIALIZE_MAP()
};
};
@@ -693,6 +705,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 +719,7 @@ namespace cryptonote
KV_SERIALIZE(hash)
KV_SERIALIZE(difficulty)
KV_SERIALIZE(reward)
+ KV_SERIALIZE(block_size)
END_KV_SERIALIZE_MAP()
};
@@ -919,7 +933,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..3412617d4 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"
@@ -71,7 +72,6 @@ typedef cryptonote::simple_wallet sw;
#define DEFAULT_MIX 4
-#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002"
#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
#define LOCK_IDLE_SCOPE() \
@@ -370,6 +370,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 +574,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 +596,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 +606,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 +626,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 +639,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 +665,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 +678,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 +691,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 +705,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 +718,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 +731,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 +1143,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 +1156,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;
@@ -1554,6 +1568,11 @@ void simple_wallet::on_money_received(uint64_t height, const cryptonote::transac
m_refresh_progress_reporter.update(height, true);
}
//----------------------------------------------------------------------------------------------------
+void simple_wallet::on_unconfirmed_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount)
+{
+ // Not implemented in CLI wallet
+}
+//----------------------------------------------------------------------------------------------------
void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx)
{
message_writer(epee::log_space::console_color_magenta, false) << "\r" <<
@@ -1877,73 +1896,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 +2090,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 +2208,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 +2583,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 +2641,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))
@@ -2757,6 +2816,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_)
local_args.push_back(amount_str);
if (!payment_id_str.empty())
local_args.push_back(payment_id_str);
+ message_writer() << tr("Donating ") << amount_str << " XMR to The Monero Project (donate.getmonero.org/44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A).";
transfer_new(local_args);
return true;
}
@@ -3074,7 +3134,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 +3442,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 +3667,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 +3872,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;
@@ -3648,23 +3906,7 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args)
try
{
- std::vector<std::pair<crypto::key_image, crypto::signature>> ski = m_wallet->export_key_images();
- std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC));
- const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address;
-
- std::string data;
- data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
- data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
- for (const auto &i: ski)
- {
- data += std::string((const char *)&i.first, sizeof(crypto::key_image));
- data += std::string((const char *)&i.second, sizeof(crypto::signature));
- }
-
- // encrypt data, keep magic plaintext
- std::string ciphertext = m_wallet->encrypt_with_view_secret_key(data);
- bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext);
- if (!r)
+ if (!m_wallet->export_key_images(filename))
{
fail_msg_writer() << tr("failed to save file ") << filename;
return true;
@@ -3690,69 +3932,17 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args)
}
std::string filename = args[0];
- std::string data;
- bool r = epee::file_io_utils::load_file_to_string(filename, data);
- if (!r)
- {
- fail_msg_writer() << tr("failed to read file ") << filename;
- return true;
- }
- const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC);
- if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen))
- {
- fail_msg_writer() << "Bad key image export file magic in " << filename;
- return true;
- }
-
- try
- {
- data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen));
- }
- catch (const std::exception &e)
- {
- fail_msg_writer() << "Failed to decrypt " << filename << ": " << e.what();
- return true;
- }
-
- const size_t headerlen = 2 * sizeof(crypto::public_key);
- if (data.size() < headerlen)
- {
- fail_msg_writer() << "Bad data size from file " << filename;
- return true;
- }
- 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)];
- const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address;
- if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key)
- {
- fail_msg_writer() << "Key images from " << filename << " are for a different account";
- return true;
- }
-
- const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature);
- if ((data.size() - headerlen) % record_size)
- {
- fail_msg_writer() << "Bad data size from file " << filename;
- return true;
- }
- size_t nki = (data.size() - headerlen) / record_size;
-
- std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
- ski.reserve(nki);
- for (size_t n = 0; n < nki; ++n)
- {
- crypto::key_image key_image = *reinterpret_cast<const crypto::key_image*>(&data[headerlen + n * record_size]);
- crypto::signature signature = *reinterpret_cast<const crypto::signature*>(&data[headerlen + n * record_size + sizeof(crypto::key_image)]);
-
- ski.push_back(std::make_pair(key_image, signature));
- }
-
try
{
uint64_t spent = 0, unspent = 0;
- uint64_t height = m_wallet->import_key_images(ski, spent, unspent);
- success_msg_writer() << "Signed key images imported to height " << height << ", "
- << print_money(spent) << " spent, " << print_money(unspent) << " unspent";
+ uint64_t height = m_wallet->import_key_images(filename, spent, unspent);
+ if (height > 0)
+ {
+ success_msg_writer() << "Signed key images imported to height " << height << ", "
+ << print_money(spent) << " spent, " << print_money(unspent) << " unspent";
+ } else {
+ fail_msg_writer() << "Failed to import key images";
+ }
}
catch (const std::exception &e)
{
@@ -3882,6 +4072,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..bf4ace948 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
@@ -182,6 +186,7 @@ namespace cryptonote
//----------------- i_wallet2_callback ---------------------
virtual void on_new_block(uint64_t height, const cryptonote::block& block);
virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount);
+ virtual void on_unconfirmed_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount);
virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx);
virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx);
//----------------------------------------------------------
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt
index 056a1ca10..53b0a794f 100644
--- a/src/wallet/CMakeLists.txt
+++ b/src/wallet/CMakeLists.txt
@@ -40,7 +40,8 @@ set(wallet_sources
api/transaction_history.cpp
api/pending_transaction.cpp
api/utils.cpp
- api/address_book.cpp)
+ api/address_book.cpp
+ api/unsigned_transaction.cpp)
set(wallet_api_headers
wallet2_api.h)
@@ -60,7 +61,8 @@ set(wallet_private_headers
api/transaction_history.h
api/pending_transaction.h
api/common_defines.h
- api/address_book.h)
+ api/address_book.h
+ api/unsigned_transaction.h)
monero_private_headers(wallet
${wallet_private_headers})
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/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp
index 6586d0c48..760c84f4f 100644
--- a/src/wallet/api/pending_transaction.cpp
+++ b/src/wallet/api/pending_transaction.cpp
@@ -51,7 +51,7 @@ PendingTransaction::~PendingTransaction() {}
PendingTransactionImpl::PendingTransactionImpl(WalletImpl &wallet)
: m_wallet(wallet)
{
-
+ m_status = Status_Ok;
}
PendingTransactionImpl::~PendingTransactionImpl()
@@ -77,19 +77,39 @@ std::vector<std::string> PendingTransactionImpl::txid() const
return txid;
}
-bool PendingTransactionImpl::commit()
+bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite)
{
LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size());
try {
+ // Save tx to file
+ if (!filename.empty()) {
+ boost::system::error_code ignore;
+ bool tx_file_exists = boost::filesystem::exists(filename, ignore);
+ if(tx_file_exists && !overwrite){
+ m_errorString = string(tr("Attempting to save transaction to file, but specified file(s) exist. Exiting to not risk overwriting. File:")) + filename;
+ m_status = Status_Error;
+ LOG_ERROR(m_errorString);
+ return false;
+ }
+ bool r = m_wallet.m_wallet->save_tx(m_pending_tx, filename);
+ if (!r) {
+ m_errorString = tr("Failed to write transaction(s) to file");
+ m_status = Status_Error;
+ } else {
+ m_status = Status_Ok;
+ }
+ }
+ // Commit tx
+ else {
while (!m_pending_tx.empty()) {
auto & ptx = m_pending_tx.back();
m_wallet.m_wallet->commit_tx(ptx);
- // success_msg_writer(true) << tr("Money successfully sent, transaction ") << get_transaction_hash(ptx.tx);
// if no exception, remove element from vector
m_pending_tx.pop_back();
} // TODO: extract method;
+ }
} catch (const tools::error::daemon_busy&) {
// TODO: make it translatable with "tr"?
m_errorString = tr("daemon is busy. Please try again later.");
@@ -100,7 +120,11 @@ bool PendingTransactionImpl::commit()
} catch (const tools::error::tx_rejected& e) {
std::ostringstream writer(m_errorString);
writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status();
+ std::string reason = e.reason();
m_status = Status_Error;
+ m_errorString = writer.str();
+ if (!reason.empty())
+ m_errorString += string(tr(". Reason: ")) + reason;
} catch (std::exception &e) {
m_errorString = string(tr("Unknown exception: ")) + e.what();
m_status = Status_Error;
diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h
index 47ccdec76..d85a686fd 100644
--- a/src/wallet/api/pending_transaction.h
+++ b/src/wallet/api/pending_transaction.h
@@ -45,7 +45,7 @@ public:
~PendingTransactionImpl();
int status() const;
std::string errorString() const;
- bool commit();
+ bool commit(const std::string &filename = "", bool overwrite = false);
uint64_t amount() const;
uint64_t dust() const;
uint64_t fee() const;
diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp
index 5f52438cd..1e50652c6 100644
--- a/src/wallet/api/transaction_history.cpp
+++ b/src/wallet/api/transaction_history.cpp
@@ -102,6 +102,7 @@ void TransactionHistoryImpl::refresh()
// TODO: configurable values;
uint64_t min_height = 0;
uint64_t max_height = (uint64_t)-1;
+ uint64_t wallet_height = m_wallet->blockChainHeight();
// delete old transactions;
for (auto t : m_history)
@@ -123,15 +124,14 @@ void TransactionHistoryImpl::refresh()
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);
- // TODO
TransactionInfoImpl * ti = new TransactionInfoImpl();
ti->m_paymentid = payment_id;
ti->m_amount = pd.m_amount;
ti->m_direction = TransactionInfo::Direction_In;
ti->m_hash = string_tools::pod_to_hex(pd.m_tx_hash);
ti->m_blockheight = pd.m_block_height;
- // TODO:
ti->m_timestamp = pd.m_timestamp;
+ ti->m_confirmations = wallet_height - pd.m_block_height;
m_history.push_back(ti);
/* output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%20.20s %s %s %s")
@@ -174,6 +174,7 @@ void TransactionHistoryImpl::refresh()
ti->m_hash = string_tools::pod_to_hex(hash);
ti->m_blockheight = pd.m_block_height;
ti->m_timestamp = pd.m_timestamp;
+ ti->m_confirmations = wallet_height - pd.m_block_height;
// single output transaction might contain multiple transfers
for (const auto &d: pd.m_dests) {
@@ -183,9 +184,9 @@ void TransactionHistoryImpl::refresh()
}
// unconfirmed output transactions
- std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments;
- m_wallet->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) {
+ std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments_out;
+ m_wallet->m_wallet->get_unconfirmed_payments_out(upayments_out);
+ for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments_out.begin(); i != upayments_out.end(); ++i) {
const tools::wallet2::unconfirmed_transfer_details &pd = i->second;
const crypto::hash &hash = i->first;
uint64_t amount = pd.m_amount_in;
@@ -204,8 +205,33 @@ void TransactionHistoryImpl::refresh()
ti->m_pending = true;
ti->m_hash = string_tools::pod_to_hex(hash);
ti->m_timestamp = pd.m_timestamp;
+ ti->m_confirmations = 0;
m_history.push_back(ti);
}
+
+
+ // unconfirmed payments (tx pool)
+ std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> upayments;
+ m_wallet->m_wallet->get_unconfirmed_payments(upayments);
+ for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
+ const tools::wallet2::payment_details &pd = i->second;
+ 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);
+ TransactionInfoImpl * ti = new TransactionInfoImpl();
+ ti->m_paymentid = payment_id;
+ ti->m_amount = pd.m_amount;
+ ti->m_direction = TransactionInfo::Direction_In;
+ ti->m_hash = string_tools::pod_to_hex(pd.m_tx_hash);
+ ti->m_blockheight = pd.m_block_height;
+ ti->m_pending = true;
+ ti->m_timestamp = pd.m_timestamp;
+ ti->m_confirmations = 0;
+ m_history.push_back(ti);
+
+ LOG_PRINT_L1(__FUNCTION__ << ": Unconfirmed payment found " << pd.m_amount);
+ }
+
}
} // namespace
diff --git a/src/wallet/api/transaction_info.cpp b/src/wallet/api/transaction_info.cpp
index 8d26f2035..576ae8532 100644
--- a/src/wallet/api/transaction_info.cpp
+++ b/src/wallet/api/transaction_info.cpp
@@ -49,6 +49,7 @@ TransactionInfoImpl::TransactionInfoImpl()
, m_fee(0)
, m_blockheight(0)
, m_timestamp(0)
+ , m_confirmations(0)
{
}
@@ -109,6 +110,11 @@ const std::vector<TransactionInfo::Transfer> &TransactionInfoImpl::transfers() c
return m_transfers;
}
+uint64_t TransactionInfoImpl::confirmations() const
+{
+ return m_confirmations;
+}
+
} // namespace
namespace Bitmonero = Monero;
diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h
index 14efebac4..af9696daf 100644
--- a/src/wallet/api/transaction_info.h
+++ b/src/wallet/api/transaction_info.h
@@ -55,6 +55,7 @@ public:
virtual std::time_t timestamp() const;
virtual std::string paymentId() const;
virtual const std::vector<Transfer> &transfers() const;
+ virtual uint64_t confirmations() const;
private:
int m_direction;
@@ -67,6 +68,7 @@ private:
std::time_t m_timestamp;
std::string m_paymentid;
std::vector<Transfer> m_transfers;
+ uint64_t m_confirmations;
friend class TransactionHistoryImpl;
diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp
new file mode 100644
index 000000000..84ec2d9d2
--- /dev/null
+++ b/src/wallet/api/unsigned_transaction.cpp
@@ -0,0 +1,279 @@
+// Copyright (c) 2014-2017, 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.
+//
+// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+
+#include "unsigned_transaction.h"
+#include "wallet.h"
+#include "common_defines.h"
+
+#include "cryptonote_core/cryptonote_format_utils.h"
+#include "cryptonote_core/cryptonote_basic_impl.h"
+#include "cryptonote_core/cryptonote_format_utils.h"
+
+#include <memory>
+#include <vector>
+#include <sstream>
+#include <boost/format.hpp>
+
+using namespace std;
+
+namespace Monero {
+
+UnsignedTransaction::~UnsignedTransaction() {}
+
+
+UnsignedTransactionImpl::UnsignedTransactionImpl(WalletImpl &wallet)
+ : m_wallet(wallet)
+{
+ m_status = Status_Ok;
+}
+
+UnsignedTransactionImpl::~UnsignedTransactionImpl()
+{
+ LOG_PRINT_L3("Unsigned tx deleted");
+}
+
+int UnsignedTransactionImpl::status() const
+{
+ return m_status;
+}
+
+string UnsignedTransactionImpl::errorString() const
+{
+ return m_errorString;
+}
+
+bool UnsignedTransactionImpl::sign(const std::string &signedFileName)
+{
+ if(m_wallet.watchOnly())
+ {
+ m_errorString = tr("This is a watch only wallet");
+ m_status = Status_Error;
+ return false;
+ }
+ std::vector<tools::wallet2::pending_tx> ptx;
+ try
+ {
+ bool r = m_wallet.m_wallet->sign_tx(m_unsigned_tx_set, signedFileName, ptx);
+ if (!r)
+ {
+ m_errorString = tr("Failed to sign transaction");
+ m_status = Status_Error;
+ return false;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ m_errorString = string(tr("Failed to sign transaction")) + e.what();
+ m_status = Status_Error;
+ return false;
+ }
+ return true;
+}
+
+//----------------------------------------------------------------------------------------------------
+bool UnsignedTransactionImpl::checkLoadedTx(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)
+{
+ // gather info to ask the user
+ uint64_t amount = 0, amount_to_dests = 0, change = 0;
+ size_t min_mixin = ~0;
+ std::unordered_map<std::string, uint64_t> dests;
+ const std::string wallet_address = m_wallet.m_wallet->get_account().get_public_address_str(m_wallet.m_wallet->testnet());
+ for (size_t n = 0; n < get_num_txes(); ++n)
+ {
+ const tools::wallet2::tx_construction_data &cd = get_tx(n);
+ for (size_t s = 0; s < cd.sources.size(); ++s)
+ {
+ amount += cd.sources[s].amount;
+ size_t mixin = cd.sources[s].outputs.size() - 1;
+ if (mixin < min_mixin)
+ min_mixin = mixin;
+ }
+ for (size_t d = 0; d < cd.splitted_dsts.size(); ++d)
+ {
+ const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d];
+ std::string address = get_account_address_as_str(m_wallet.m_wallet->testnet(), entry.addr);
+ std::unordered_map<std::string,uint64_t>::iterator i = dests.find(address);
+ if (i == dests.end())
+ dests.insert(std::make_pair(address, entry.amount));
+ else
+ i->second += entry.amount;
+ amount_to_dests += entry.amount;
+ }
+ if (cd.change_dts.amount > 0)
+ {
+ std::unordered_map<std::string, uint64_t>::iterator it = dests.find(get_account_address_as_str(m_wallet.m_wallet->testnet(), cd.change_dts.addr));
+ if (it == dests.end())
+ {
+ m_status = Status_Error;
+ m_errorString = tr("Claimed change does not go to a paid address");
+ return false;
+ }
+ if (it->second < cd.change_dts.amount)
+ {
+ m_status = Status_Error;
+ m_errorString = tr("Claimed change is larger than payment to the change address");
+ return false;
+ }
+ if (memcmp(&cd.change_dts.addr, &get_tx(0).change_dts.addr, sizeof(cd.change_dts.addr)))
+ {
+ m_status = Status_Error;
+ m_errorString = tr("Change does to more than one address");
+ return false;
+ }
+ change += cd.change_dts.amount;
+ it->second -= cd.change_dts.amount;
+ if (it->second == 0)
+ dests.erase(get_account_address_as_str(m_wallet.m_wallet->testnet(), cd.change_dts.addr));
+ }
+ }
+ std::string dest_string;
+ for (std::unordered_map<std::string, uint64_t>::const_iterator i = dests.begin(); i != dests.end(); )
+ {
+ dest_string += (boost::format(tr("sending %s to %s")) % cryptonote::print_money(i->second) % i->first).str();
+ ++i;
+ if (i != dests.end())
+ dest_string += ", ";
+ }
+ if (dest_string.empty())
+ dest_string = tr("with no destinations");
+
+ std::string change_string;
+ if (change > 0)
+ {
+ std::string address = get_account_address_as_str(m_wallet.m_wallet->testnet(), get_tx(0).change_dts.addr);
+ change_string += (boost::format(tr("%s change to %s")) % cryptonote::print_money(change) % address).str();
+ }
+ else
+ change_string += tr("no change");
+ uint64_t fee = amount - amount_to_dests;
+ m_confirmationMessage = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, %s, %s, with min mixin %lu. %s")) % (unsigned long)get_num_txes() % cryptonote::print_money(amount) % cryptonote::print_money(fee) % dest_string % change_string % (unsigned long)min_mixin % extra_message).str();
+ return true;
+}
+
+std::vector<uint64_t> UnsignedTransactionImpl::amount() const
+{
+ std::vector<uint64_t> result;
+ for (const auto &utx : m_unsigned_tx_set.txes) {
+ for (const auto &unsigned_dest : utx.dests) {
+ result.push_back(unsigned_dest.amount);
+ }
+ }
+ return result;
+}
+
+std::vector<uint64_t> UnsignedTransactionImpl::fee() const
+{
+ std::vector<uint64_t> result;
+ for (const auto &utx : m_unsigned_tx_set.txes) {
+ uint64_t fee = 0;
+ for (const auto &i: utx.sources) fee += i.amount;
+ for (const auto &i: utx.splitted_dsts) fee -= i.amount;
+ result.push_back(fee);
+ }
+ return result;
+}
+
+std::vector<uint64_t> UnsignedTransactionImpl::mixin() const
+{
+ std::vector<uint64_t> result;
+ for (const auto &utx: m_unsigned_tx_set.txes) {
+ size_t min_mixin = ~0;
+ // TODO: Is this loop needed or is sources[0] ?
+ for (size_t s = 0; s < utx.sources.size(); ++s) {
+ size_t mixin = utx.sources[s].outputs.size() - 1;
+ if (mixin < min_mixin)
+ min_mixin = mixin;
+ }
+ result.push_back(min_mixin);
+ }
+ return result;
+}
+
+uint64_t UnsignedTransactionImpl::txCount() const
+{
+ return m_unsigned_tx_set.txes.size();
+}
+
+std::vector<std::string> UnsignedTransactionImpl::paymentId() const
+{
+ std::vector<string> result;
+ for (const auto &utx: m_unsigned_tx_set.txes) {
+ crypto::hash payment_id = cryptonote::null_hash;
+ cryptonote::tx_extra_nonce extra_nonce;
+ std::vector<cryptonote::tx_extra_field> tx_extra_fields;
+ cryptonote::parse_tx_extra(utx.extra, tx_extra_fields);
+ if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
+ {
+ crypto::hash8 payment_id8 = cryptonote::null_hash8;
+ if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
+ {
+ // We can't decrypt short pid without recipient key.
+ memcpy(payment_id.data, payment_id8.data, 8);
+ }
+ else if (!cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
+ {
+ payment_id = cryptonote::null_hash;
+ }
+ }
+ if(payment_id != cryptonote::null_hash)
+ result.push_back(epee::string_tools::pod_to_hex(payment_id));
+ else
+ result.push_back("");
+ }
+ return result;
+}
+
+std::vector<std::string> UnsignedTransactionImpl::recipientAddress() const
+{
+ // TODO: return integrated address if short payment ID exists
+ std::vector<string> result;
+ for (const auto &utx: m_unsigned_tx_set.txes) {
+ result.push_back(cryptonote::get_account_address_as_str(m_wallet.m_wallet->testnet(), utx.dests[0].addr));
+ }
+ return result;
+}
+
+uint64_t UnsignedTransactionImpl::minMixinCount() const
+{
+ uint64_t min_mixin = ~0;
+ for (const auto &utx: m_unsigned_tx_set.txes) {
+ for (size_t s = 0; s < utx.sources.size(); ++s) {
+ size_t mixin = utx.sources[s].outputs.size() - 1;
+ if (mixin < min_mixin)
+ min_mixin = mixin;
+ }
+ }
+ return min_mixin;
+}
+
+} // namespace
+
+namespace Bitmonero = Monero;
+
diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h
new file mode 100644
index 000000000..8038334e4
--- /dev/null
+++ b/src/wallet/api/unsigned_transaction.h
@@ -0,0 +1,76 @@
+// 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.
+//
+// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+
+#include "wallet/wallet2_api.h"
+#include "wallet/wallet2.h"
+
+#include <string>
+#include <vector>
+
+
+namespace Monero {
+
+class WalletImpl;
+class UnsignedTransactionImpl : public UnsignedTransaction
+{
+public:
+ UnsignedTransactionImpl(WalletImpl &wallet);
+ ~UnsignedTransactionImpl();
+ int status() const;
+ std::string errorString() const;
+ std::vector<uint64_t> amount() const;
+ std::vector<uint64_t> dust() const;
+ std::vector<uint64_t> fee() const;
+ std::vector<uint64_t> mixin() const;
+ std::vector<std::string> paymentId() const;
+ std::vector<std::string> recipientAddress() const;
+ uint64_t txCount() const;
+ // sign txs and save to file
+ bool sign(const std::string &signedFileName);
+ std::string confirmationMessage() const {return m_confirmationMessage;}
+ uint64_t minMixinCount() const;
+
+private:
+ // Callback function to check all loaded tx's and generate confirmationMessage
+ bool checkLoadedTx(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);
+
+ friend class WalletImpl;
+ WalletImpl &m_wallet;
+
+ int m_status;
+ std::string m_errorString;
+ tools::wallet2::unsigned_tx_set m_unsigned_tx_set;
+ std::string m_confirmationMessage;
+};
+
+
+}
+
+namespace Bitmonero = Monero;
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 3a4493ec3..e8ae7c642 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -31,6 +31,7 @@
#include "wallet.h"
#include "pending_transaction.h"
+#include "unsigned_transaction.h"
#include "transaction_history.h"
#include "address_book.h"
#include "common_defines.h"
@@ -51,6 +52,8 @@ namespace {
static const int DEFAULT_REFRESH_INTERVAL_MILLIS = 1000 * 10;
// limit maximum refresh interval as one minute
static const int MAX_REFRESH_INTERVAL_MILLIS = 1000 * 60 * 1;
+ // Default refresh interval when connected to remote node
+ static const int DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS = 1000 * 10;
}
struct Wallet2CallbackImpl : public tools::i_wallet2_callback
@@ -103,6 +106,21 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
}
}
+ virtual void on_unconfirmed_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount)
+ {
+
+ std::string tx_hash = epee::string_tools::pod_to_hex(get_transaction_hash(tx));
+
+ LOG_PRINT_L3(__FUNCTION__ << ": unconfirmed money received. height: " << height
+ << ", tx: " << tx_hash
+ << ", amount: " << print_money(amount));
+ // do not signal on received tx if wallet is not syncronized completely
+ if (m_listener && m_wallet->synchronized()) {
+ m_listener->unconfirmedMoneyReceived(tx_hash, amount);
+ m_listener->updated();
+ }
+ }
+
virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount,
const cryptonote::transaction& spend_tx)
{
@@ -277,6 +295,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 +473,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();
@@ -578,6 +641,50 @@ int WalletImpl::autoRefreshInterval() const
return m_refreshIntervalMillis;
}
+UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_filename) {
+ clearStatus();
+ UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this);
+ if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){
+ m_errorString = tr("Failed to load unsigned transactions");
+ m_status = Status_Error;
+ }
+
+ // Check tx data and construct confirmation message
+ std::string extra_message;
+ if (!transaction->m_unsigned_tx_set.transfers.empty())
+ extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.size()).str();
+ transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message);
+ m_status = transaction->status();
+ m_errorString = transaction->errorString();
+
+ return transaction;
+}
+
+bool WalletImpl::submitTransaction(const string &fileName) {
+ clearStatus();
+ PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
+
+// bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx, [&](const tools::wallet2::signed_tx_set &tx){ return accept_loaded_tx(tx); });
+ bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx);
+ if (!r) {
+ m_errorString = tr("Failed to load transaction from file");
+ m_status = Status_Ok;
+ delete transaction;
+ return false;
+ }
+
+ if(!transaction->commit()) {
+ m_errorString = transaction->m_errorString;
+ m_status = Status_Error;
+ delete transaction;
+ return false;
+ }
+
+ delete transaction;
+
+ return true;
+}
+
// TODO:
// 1 - properly handle payment id (add another menthod with explicit 'payment_id' param)
// 2 - check / design how "Transaction" can be single interface
@@ -595,6 +702,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
clearStatus();
// Pause refresh thread while creating transaction
pauseRefresh();
+
cryptonote::account_public_address addr;
// indicates if dst_addr is integrated address (address + payment_id)
@@ -966,7 +1074,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 +1123,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 +1133,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 +1182,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 +1192,31 @@ 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);
+ m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS;
+ } else {
+ this->setTrustedDaemon(false);
+ m_refreshIntervalMillis = DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS;
}
+
}
+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..5b4064e8e 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -43,6 +43,7 @@
namespace Monero {
class TransactionHistoryImpl;
class PendingTransactionImpl;
+class UnsignedTransactionImpl;
class AddressBookImpl;
struct Wallet2CallbackImpl;
@@ -53,6 +54,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 +68,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 +92,7 @@ public:
int autoRefreshInterval() const;
void setRefreshFromBlockHeight(uint64_t refresh_from_block_height);
void setRecoveringFromSeed(bool recoveringFromSeed);
+ bool watchOnly() const;
@@ -95,6 +100,8 @@ public:
optional<uint64_t> amount, uint32_t mixin_count,
PendingTransaction::Priority priority = PendingTransaction::Priority_Low);
virtual PendingTransaction * createSweepUnmixableTransaction();
+ bool submitTransaction(const std::string &fileName);
+ virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename);
virtual void disposeTransaction(PendingTransaction * t);
virtual TransactionHistory * history() const;
@@ -109,9 +116,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;
@@ -121,6 +129,7 @@ private:
private:
friend class PendingTransactionImpl;
+ friend class UnsignedTransactionImpl;
friend class TransactionHistoryImpl;
friend class Wallet2CallbackImpl;
friend class AddressBookImpl;
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..0ad74f52a 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
@@ -93,6 +92,8 @@ using namespace cryptonote;
ioservice.stop(); \
} while(0)
+#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002"
+
namespace
{
// Create on-demand to prevent static initialization order fiasco issues.
@@ -1005,8 +1006,11 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s
payment.m_block_height = height;
payment.m_unlock_time = tx.unlock_time;
payment.m_timestamp = ts;
- if (pool)
+ if (pool) {
m_unconfirmed_payments.emplace(payment_id, payment);
+ if (0 != m_callback)
+ m_callback->on_unconfirmed_money_received(height, tx, payment.m_amount);
+ }
else
m_payments.emplace(payment_id, payment);
LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount);
@@ -1613,6 +1617,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 +1639,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 +1675,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 +1813,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 +1898,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 +1929,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 +2154,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 +2869,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;
@@ -2946,6 +2894,24 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const
}
return payment_id;
}
+
+crypto::hash8 wallet2::get_short_payment_id(const pending_tx &ptx) const
+{
+ crypto::hash8 payment_id8 = null_hash8;
+ std::vector<tx_extra_field> tx_extra_fields;
+ if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields))
+ return payment_id8;
+ cryptonote::tx_extra_nonce extra_nonce;
+ if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
+ {
+ if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
+ {
+ decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key);
+ }
+ }
+ return payment_id8;
+}
+
//----------------------------------------------------------------------------------------------------
// take a pending tx and actually send it to the daemon
void wallet2::commit_tx(pending_tx& ptx)
@@ -3016,19 +2982,47 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri
LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions");
unsigned_tx_set txs;
for (auto &tx: ptx_vector)
- txs.txes.push_back(tx.construction_data);
+ {
+ tx_construction_data construction_data = tx.construction_data;
+ // Short payment id is encrypted with tx_key.
+ // Since sign_tx() generates new tx_keys and encrypts the payment id, we need to save the decrypted payment ID
+ // Get decrypted payment id from pending_tx
+ crypto::hash8 payment_id = get_short_payment_id(tx);
+ if (payment_id != null_hash8)
+ {
+ // Remove encrypted
+ remove_field_from_tx_extra(construction_data.extra, typeid(cryptonote::tx_extra_nonce));
+ // Add decrypted
+ std::string extra_nonce;
+ set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id);
+ if (!add_extra_nonce_to_tx_extra(construction_data.extra, extra_nonce))
+ {
+ LOG_ERROR("Failed to add decrypted payment id to tx extra");
+ return false;
+ }
+ LOG_PRINT_L1("Decrypted payment ID: " << payment_id);
+ }
+ // Save tx construction_data to unsigned_tx_set
+ txs.txes.push_back(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)
+bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs)
{
std::string s;
boost::system::error_code errcode;
@@ -3049,20 +3043,40 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
LOG_PRINT_L0("Bad magic from " << unsigned_filename);
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;
}
LOG_PRINT_L1("Loaded tx unsigned data from binary: " << exported_txs.txes.size() << " transactions");
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+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)
+{
+ unsigned_tx_set exported_txs;
+ if(!load_unsigned_tx(unsigned_filename, exported_txs))
+ return false;
+
if (accept_func && !accept_func(exported_txs))
{
LOG_PRINT_L1("Transactions rejected by callback");
return false;
}
+ return sign_tx(exported_txs, signed_filename, txs);
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs)
+{
import_outputs(exported_txs.transfers);
// sign the transactions
@@ -3123,14 +3137,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_L3("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 +3175,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 +3547,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));
@@ -4800,6 +4843,27 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
"Public key yielding at least one output wasn't found in the transaction extra");
return cryptonote::null_pkey;
}
+
+bool wallet2::export_key_images(const std::string filename)
+{
+ std::vector<std::pair<crypto::key_image, crypto::signature>> ski = export_key_images();
+ std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC));
+ const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
+
+ std::string data;
+ data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
+ data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
+ for (const auto &i: ski)
+ {
+ data += std::string((const char *)&i.first, sizeof(crypto::key_image));
+ data += std::string((const char *)&i.second, sizeof(crypto::signature));
+ }
+
+ // encrypt data, keep magic plaintext
+ std::string ciphertext = encrypt_with_view_secret_key(data);
+ return epee::file_io_utils::save_string_to_file(filename, magic + ciphertext);
+}
+
//----------------------------------------------------------------------------------------------------
std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key_images() const
{
@@ -4850,6 +4914,70 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key
}
return ski;
}
+
+uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent)
+{
+ std::string data;
+ bool r = epee::file_io_utils::load_file_to_string(filename, data);
+
+ if (!r)
+ {
+ fail_msg_writer() << tr("failed to read file ") << filename;
+ return 0;
+ }
+ const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC);
+ if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen))
+ {
+ fail_msg_writer() << "Bad key image export file magic in " << filename;
+ return 0;
+ }
+
+ try
+ {
+ data = decrypt_with_view_secret_key(std::string(data, magiclen));
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << "Failed to decrypt " << filename << ": " << e.what();
+ return 0;
+ }
+
+ const size_t headerlen = 2 * sizeof(crypto::public_key);
+ if (data.size() < headerlen)
+ {
+ fail_msg_writer() << "Bad data size from file " << filename;
+ return 0;
+ }
+ 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)];
+ const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
+ if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key)
+ {
+ fail_msg_writer() << "Key images from " << filename << " are for a different account";
+ return 0;
+ }
+
+ const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature);
+ if ((data.size() - headerlen) % record_size)
+ {
+ fail_msg_writer() << "Bad data size from file " << filename;
+ return 0;
+ }
+ size_t nki = (data.size() - headerlen) / record_size;
+
+ std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
+ ski.reserve(nki);
+ for (size_t n = 0; n < nki; ++n)
+ {
+ crypto::key_image key_image = *reinterpret_cast<const crypto::key_image*>(&data[headerlen + n * record_size]);
+ crypto::signature signature = *reinterpret_cast<const crypto::signature*>(&data[headerlen + n * record_size + sizeof(crypto::key_image)]);
+
+ ski.push_back(std::make_pair(key_image, signature));
+ }
+
+ return import_key_images(ski, spent, unspent);
+}
+
//----------------------------------------------------------------------------------------------------
uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent)
{
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 54e26008b..7f25673d6 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
@@ -65,6 +67,7 @@ namespace tools
public:
virtual void on_new_block(uint64_t height, const cryptonote::block& block) {}
virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount) {}
+ virtual void on_unconfirmed_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount) {}
virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) {}
virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx) {}
virtual ~i_wallet2_callback() {}
@@ -86,6 +89,7 @@ namespace tools
class wallet2
{
+ friend class ::Serialization_portability_wallet_Test;
public:
enum RefreshType {
RefreshFull,
@@ -95,7 +99,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 +120,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 +205,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 +225,20 @@ 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()
};
+ // The term "Unsigned tx" is not really a tx since it's not signed yet.
+ // It doesnt have tx hash, key and the integrated address is not separated into addr + payment id.
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 +328,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
@@ -415,7 +390,12 @@ namespace tools
void commit_tx(pending_tx& ptx_vector);
void commit_tx(std::vector<pending_tx>& ptx_vector);
bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename);
+ // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet
bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL);
+ // sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI
+ bool sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx);
+ // load unsigned_tx_set from file.
+ bool load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs);
bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL);
std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon);
std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon);
@@ -503,12 +483,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; }
@@ -561,8 +539,10 @@ namespace tools
std::vector<tools::wallet2::transfer_details> export_outputs() const;
size_t import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs);
+ bool export_key_images(const std::string filename);
std::vector<std::pair<crypto::key_image, crypto::signature>> export_key_images() const;
uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent);
+ uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent);
void update_pool_state();
@@ -609,6 +589,7 @@ namespace tools
void check_genesis(const crypto::hash& genesis_hash) const; //throws
bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const;
crypto::hash get_payment_id(const pending_tx &ptx) const;
+ crypto::hash8 get_short_payment_id(const pending_tx &ptx) const;
void check_acc_out_precomp(const crypto::public_key &spend_public_key, const cryptonote::tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const;
void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const;
uint64_t get_upper_tranaction_size_limit();
@@ -657,6 +638,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 +654,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 +855,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..daffb48bf 100644
--- a/src/wallet/wallet2_api.h
+++ b/src/wallet/wallet2_api.h
@@ -77,7 +77,8 @@ struct PendingTransaction
virtual ~PendingTransaction() = 0;
virtual int status() const = 0;
virtual std::string errorString() const = 0;
- virtual bool commit() = 0;
+ // commit transaction or save to file if filename is provided.
+ virtual bool commit(const std::string &filename = "", bool overwrite = false) = 0;
virtual uint64_t amount() const = 0;
virtual uint64_t dust() const = 0;
virtual uint64_t fee() const = 0;
@@ -90,6 +91,48 @@ struct PendingTransaction
};
/**
+ * @brief Transaction-like interface for sending money
+ */
+struct UnsignedTransaction
+{
+ enum Status {
+ Status_Ok,
+ Status_Error,
+ Status_Critical
+ };
+
+ enum Priority {
+ Priority_Low = 1,
+ Priority_Medium = 2,
+ Priority_High = 3,
+ Priority_Last
+ };
+
+ virtual ~UnsignedTransaction() = 0;
+ virtual int status() const = 0;
+ virtual std::string errorString() const = 0;
+ virtual std::vector<uint64_t> amount() const = 0;
+ virtual std::vector<uint64_t> fee() const = 0;
+ virtual std::vector<uint64_t> mixin() const = 0;
+ // returns a string with information about all transactions.
+ virtual std::string confirmationMessage() const = 0;
+ virtual std::vector<std::string> paymentId() const = 0;
+ virtual std::vector<std::string> recipientAddress() const = 0;
+ virtual uint64_t minMixinCount() const = 0;
+ /*!
+ * \brief txCount - number of transactions current transaction will be splitted to
+ * \return
+ */
+ virtual uint64_t txCount() const = 0;
+ /*!
+ * @brief sign - Sign txs and saves to file
+ * @param signedFileName
+ * return - true on success
+ */
+ virtual bool sign(const std::string &signedFileName) = 0;
+};
+
+/**
* @brief The TransactionInfo - interface for displaying transaction information
*/
struct TransactionInfo
@@ -112,6 +155,7 @@ struct TransactionInfo
virtual uint64_t amount() const = 0;
virtual uint64_t fee() const = 0;
virtual uint64_t blockHeight() const = 0;
+ virtual uint64_t confirmations() const = 0;
//! transaction_id
virtual std::string hash() const = 0;
virtual std::time_t timestamp() const = 0;
@@ -175,6 +219,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
@@ -193,6 +238,13 @@ struct WalletListener
* @param amount - amount
*/
virtual void moneyReceived(const std::string &txId, uint64_t amount) = 0;
+
+ /**
+ * @brief unconfirmedMoneyReceived - called when payment arrived in tx pool
+ * @param txId - transaction id
+ * @param amount - amount
+ */
+ virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) = 0;
/**
* @brief newBlock - called when new block received
@@ -255,6 +307,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 +352,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 +390,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
@@ -419,12 +492,27 @@ struct Wallet
*/
virtual PendingTransaction * createSweepUnmixableTransaction() = 0;
+
+ /*!
+ * \brief loadUnsignedTx - creates transaction from unsigned tx file
+ * \return - UnsignedTransaction object. caller is responsible to check UnsignedTransaction::status()
+ * after object returned
+ */
+ virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0;
+
+ /*!
+ * \brief submitTransaction - submits transaction in signed tx file
+ * \return - true on success
+ */
+ virtual bool submitTransaction(const std::string &fileName) = 0;
+
/*!
* \brief disposeTransaction - destroys transaction object
* \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned;
*/
virtual void disposeTransaction(PendingTransaction * t) = 0;
+
virtual TransactionHistory * history() const = 0;
virtual AddressBook * addressBook() const = 0;
virtual void setListener(WalletListener *) = 0;
@@ -468,6 +556,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 +652,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/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp
index 671832ad0..6f33684cd 100644
--- a/tests/libwallet_api_tests/main.cpp
+++ b/tests/libwallet_api_tests/main.cpp
@@ -831,6 +831,16 @@ struct MyWalletListener : public Monero::WalletListener
cv_receive.notify_one();
}
+ virtual void unconfirmedMoneyReceived(const string &txId, uint64_t amount)
+ {
+ std::cout << "wallet: " << wallet->address() << "**** just received unconfirmed money ("
+ << txId << ", " << wallet->displayAmount(amount) << ")" << std::endl;
+ // Don't trigger recieve until tx is mined
+ // total_rx += amount;
+ // receive_triggered = true;
+ // cv_receive.notify_one();
+ }
+
virtual void newBlock(uint64_t height)
{
// std::cout << "wallet: " << wallet->address()
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 a854b7b52..be17f01ad 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");
+}