aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt13
-rw-r--r--contrib/epee/include/net/http_auth.h115
-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--src/blockchain_db/berkeleydb/db_bdb.cpp3
-rw-r--r--src/blockchain_db/berkeleydb/db_bdb.h2
-rw-r--r--src/blockchain_db/blockchain_db.h9
-rw-r--r--src/blockchain_db/db_types.h1
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp56
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h3
-rw-r--r--src/blockchain_utilities/fake_core.h4
-rw-r--r--src/common/command_line.cpp10
-rw-r--r--src/common/command_line.h1
-rw-r--r--src/cryptonote_core/account.cpp8
-rw-r--r--src/cryptonote_core/blockchain.cpp60
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp89
-rw-r--r--src/cryptonote_core/cryptonote_core.h17
-rw-r--r--src/cryptonote_core/hardfork.cpp13
-rw-r--r--src/cryptonote_core/hardfork.h15
-rw-r--r--src/cryptonote_core/tx_pool.cpp48
-rw-r--r--src/cryptonote_core/tx_pool.h29
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl8
-rw-r--r--src/daemon/command_parser_executor.cpp18
-rw-r--r--src/daemon/command_parser_executor.h2
-rw-r--r--src/daemon/command_server.cpp5
-rw-r--r--src/daemon/rpc_command_executor.cpp112
-rw-r--r--src/daemon/rpc_command_executor.h2
-rw-r--r--src/mnemonics/electrum-words.cpp27
-rw-r--r--src/ringct/rctSigs.cpp173
-rw-r--r--src/ringct/rctSigs.h6
-rw-r--r--src/rpc/core_rpc_server.cpp9
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h16
-rw-r--r--src/simplewallet/simplewallet.cpp255
-rw-r--r--src/simplewallet/simplewallet.h5
-rw-r--r--src/wallet/CMakeLists.txt6
-rw-r--r--src/wallet/api/address_book.cpp54
-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.cpp202
-rw-r--r--src/wallet/api/wallet.h15
-rw-r--r--src/wallet/api/wallet_manager.cpp38
-rw-r--r--src/wallet/api/wallet_manager.h3
-rw-r--r--src/wallet/wallet2.cpp225
-rw-r--r--src/wallet/wallet2.h25
-rw-r--r--src/wallet/wallet2_api.h118
-rw-r--r--src/wallet/wallet_rpc_server.cpp2
-rw-r--r--tests/core_proxy/core_proxy.cpp2
-rw-r--r--tests/core_proxy/core_proxy.h2
-rw-r--r--tests/core_tests/chaingen.h4
-rw-r--r--tests/libwallet_api_tests/main.cpp10
-rw-r--r--tests/unit_tests/ban.cpp2
-rw-r--r--tests/unit_tests/hardfork.cpp2
-rw-r--r--tests/unit_tests/http_auth.cpp351
-rw-r--r--tests/unit_tests/main.cpp1
-rw-r--r--tests/unit_tests/mnemonics.cpp19
-rw-r--r--tests/unit_tests/serialization.cpp8
62 files changed, 2608 insertions, 557 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 843f88c87..4a4d2781b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -273,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})
diff --git a/contrib/epee/include/net/http_auth.h b/contrib/epee/include/net/http_auth.h
index 795d213d9..bdbfa7524 100644
--- a/contrib/epee/include/net/http_auth.h
+++ b/contrib/epee/include/net/http_auth.h
@@ -28,32 +28,35 @@
#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
{
- //! Implements RFC 2617 digest auth. Digests from RFC 7616 can be added.
- class http_auth
+ struct login
{
- public:
- struct login
- {
- login() : username(), password() {}
- login(std::string username_, std::string password_)
- : username(std::move(username_)), password(std::move(password_))
- {}
+ login() : username(), password() {}
+ login(std::string username_, std::string password_)
+ : username(std::move(username_)), password(std::move(password_))
+ {}
- std::string username;
- std::string password;
- };
+ std::string username;
+ std::string password;
+ };
+ //! Implements RFC 2617 digest auth. Digests from RFC 7616 can be added.
+ class http_server_auth
+ {
+ public:
struct session
{
session(login credentials_)
@@ -65,21 +68,97 @@ namespace net_utils
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/src/blockchain_db/berkeleydb/db_bdb.cpp b/src/blockchain_db/berkeleydb/db_bdb.cpp
index 137ed9dc6..57d8371bd 100644
--- a/src/blockchain_db/berkeleydb/db_bdb.cpp
+++ b/src/blockchain_db/berkeleydb/db_bdb.cpp
@@ -1813,9 +1813,10 @@ bool BlockchainBDB::has_key_image(const crypto::key_image& img) const
// Ostensibly BerkeleyDB has batch transaction support built-in,
// so the following few functions will be NOP.
-void BlockchainBDB::batch_start(uint64_t batch_num_blocks)
+bool BlockchainBDB::batch_start(uint64_t batch_num_blocks)
{
LOG_PRINT_L3("BlockchainBDB::" << __func__);
+ return false;
}
void BlockchainBDB::batch_commit()
diff --git a/src/blockchain_db/berkeleydb/db_bdb.h b/src/blockchain_db/berkeleydb/db_bdb.h
index f320ab0e3..266e780c6 100644
--- a/src/blockchain_db/berkeleydb/db_bdb.h
+++ b/src/blockchain_db/berkeleydb/db_bdb.h
@@ -324,7 +324,7 @@ public:
);
virtual void set_batch_transactions(bool batch_transactions);
- virtual void batch_start(uint64_t batch_num_blocks=0);
+ virtual bool batch_start(uint64_t batch_num_blocks=0);
virtual void batch_commit();
virtual void batch_stop();
virtual void batch_abort();
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index b39cb1801..455e0c811 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -655,16 +655,17 @@ public:
* been called. In either case, it should end the batch and write to its
* backing store.
*
- * If a batch is already in-progress, this function should throw a DB_ERROR.
- * This exception may change in the future if it is deemed necessary to
- * have a more granular exception type for this scenario.
+ * If a batch is already in-progress, this function must return false.
+ * If a batch was started by this call, it must return true.
*
* If any of this cannot be done, the subclass should throw the corresponding
* subclass of DB_EXCEPTION
*
* @param batch_num_blocks number of blocks to batch together
+ *
+ * @return true if we started the batch, false if already started
*/
- virtual void batch_start(uint64_t batch_num_blocks=0) = 0;
+ virtual bool batch_start(uint64_t batch_num_blocks=0) = 0;
/**
* @brief ends a batch transaction
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 ba2cb60bd..ca79ab4f8 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -543,6 +543,7 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) con
uint64_t min_block_size = 4 * 1024;
uint64_t block_stop = 0;
+ uint64_t m_height = height();
if (m_height > 1)
block_stop = m_height - 1;
uint64_t block_start = 0;
@@ -593,6 +594,7 @@ void BlockchainLMDB::add_block(const block& blk, const size_t& block_size, const
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
mdb_txn_cursors *m_cursors = &m_wcursors;
+ uint64_t m_height = height();
CURSOR(block_heights)
blk_height bh = {blk_hash, m_height};
@@ -654,6 +656,7 @@ void BlockchainLMDB::remove_block()
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
+ uint64_t m_height = height();
if (m_height == 0)
throw0(BLOCK_DNE ("Attempting to remove block from an empty blockchain"));
@@ -691,6 +694,7 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
mdb_txn_cursors *m_cursors = &m_wcursors;
+ uint64_t m_height = height();
int result;
uint64_t tx_id = m_num_txs;
@@ -787,6 +791,7 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash,
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
mdb_txn_cursors *m_cursors = &m_wcursors;
+ uint64_t m_height = height();
int result = 0;
@@ -1018,7 +1023,6 @@ BlockchainLMDB::BlockchainLMDB(bool batch_transactions)
m_write_txn = nullptr;
m_write_batch_txn = nullptr;
m_batch_active = false;
- m_height = 0;
m_cum_size = 0;
m_cum_count = 0;
@@ -1143,7 +1147,7 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags)
if ((result = mdb_stat(txn, m_blocks, &db_stats)))
throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str()));
LOG_PRINT_L2("Setting m_height to: " << db_stats.ms_entries);
- m_height = db_stats.ms_entries;
+ uint64_t m_height = db_stats.ms_entries;
// get and keep current number of txs
if ((result = mdb_stat(txn, m_txs, &db_stats)))
@@ -1294,7 +1298,6 @@ void BlockchainLMDB::reset()
throw0(DB_ERROR(lmdb_error("Failed to write version to database: ", result).c_str()));
txn.commit();
- m_height = 0;
m_num_outputs = 0;
m_cum_size = 0;
m_cum_count = 0;
@@ -1515,6 +1518,7 @@ uint64_t BlockchainLMDB::get_top_block_timestamp() const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
+ uint64_t m_height = height();
// if no blocks, return 0
if (m_height == 0)
@@ -1666,6 +1670,7 @@ crypto::hash BlockchainLMDB::top_block_hash() const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
+ uint64_t m_height = height();
if (m_height != 0)
{
return get_block_hash_from_height(m_height - 1);
@@ -1678,6 +1683,7 @@ block BlockchainLMDB::get_top_block() const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
+ uint64_t m_height = height();
if (m_height != 0)
{
@@ -1692,8 +1698,14 @@ uint64_t BlockchainLMDB::height() const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
+ TXN_PREFIX_RDONLY();
+ int result;
- return m_height;
+ // get current height
+ MDB_stat db_stats;
+ if ((result = mdb_stat(m_txn, m_blocks, &db_stats)))
+ throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str()));
+ return db_stats.ms_entries;
}
bool BlockchainLMDB::tx_exists(const crypto::hash& h) const
@@ -2242,15 +2254,15 @@ bool BlockchainLMDB::for_all_outputs(std::function<bool(uint64_t amount, const c
}
// batch_num_blocks: (optional) Used to check if resize needed before batch transaction starts.
-void BlockchainLMDB::batch_start(uint64_t batch_num_blocks)
+bool BlockchainLMDB::batch_start(uint64_t batch_num_blocks)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
if (! m_batch_transactions)
throw0(DB_ERROR("batch transactions not enabled"));
if (m_batch_active)
- throw0(DB_ERROR("batch transaction already in progress"));
+ return false;
if (m_write_batch_txn != nullptr)
- throw0(DB_ERROR("batch transaction already in progress"));
+ return false;
if (m_write_txn)
throw0(DB_ERROR("batch transaction attempted, but m_write_txn already in use"));
check_open();
@@ -2276,6 +2288,7 @@ void BlockchainLMDB::batch_start(uint64_t batch_num_blocks)
memset(&m_wcursors, 0, sizeof(m_wcursors));
LOG_PRINT_L3("batch transaction: begin");
+ return true;
}
void BlockchainLMDB::batch_commit()
@@ -2287,6 +2300,9 @@ void BlockchainLMDB::batch_commit()
throw0(DB_ERROR("batch transaction not in progress"));
if (m_write_batch_txn == nullptr)
throw0(DB_ERROR("batch transaction not in progress"));
+ if (m_writer != boost::this_thread::get_id())
+ return; // batch txn owned by other thread
+
check_open();
LOG_PRINT_L3("batch transaction: committing...");
@@ -2311,6 +2327,8 @@ void BlockchainLMDB::batch_stop()
throw0(DB_ERROR("batch transaction not in progress"));
if (m_write_batch_txn == nullptr)
throw0(DB_ERROR("batch transaction not in progress"));
+ if (m_writer != boost::this_thread::get_id())
+ return; // batch txn owned by other thread
check_open();
LOG_PRINT_L3("batch transaction: committing...");
TIME_MEASURE_START(time1);
@@ -2333,6 +2351,8 @@ void BlockchainLMDB::batch_abort()
throw0(DB_ERROR("batch transactions not enabled"));
if (! m_batch_active)
throw0(DB_ERROR("batch transaction not in progress"));
+ if (m_writer != boost::this_thread::get_id())
+ return; // batch txn owned by other thread
check_open();
// for destruction of batch transaction
m_write_txn = nullptr;
@@ -2505,6 +2525,7 @@ uint64_t BlockchainLMDB::add_block(const block& blk, const size_t& block_size, c
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
+ uint64_t m_height = height();
if (m_height % 1000 == 0)
{
@@ -2558,8 +2579,6 @@ void BlockchainLMDB::pop_block(block& blk, std::vector<transaction>& txs)
block_txn_abort();
throw;
}
-
- --m_height;
}
void BlockchainLMDB::get_output_tx_and_index_from_global(const std::vector<uint64_t> &global_indices,
@@ -2850,7 +2869,7 @@ void BlockchainLMDB::fixup()
void BlockchainLMDB::migrate_0_1()
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
- uint64_t i, z;
+ uint64_t i, z, m_height;
int result;
mdb_txn_safe txn(false);
MDB_val k, v;
@@ -2859,17 +2878,22 @@ void BlockchainLMDB::migrate_0_1()
LOG_PRINT_YELLOW("Migrating blockchain from DB version 0 to 1 - this may take a while:", LOG_LEVEL_0);
LOG_PRINT_L0("updating blocks, hf_versions, outputs, txs, and spent_keys tables...");
- LOG_PRINT_L0("Total number of blocks: " << m_height);
- LOG_PRINT_L1("block migration will update block_heights, block_info, and hf_versions...");
-
do {
+ result = mdb_txn_begin(m_env, NULL, 0, txn);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
+
+ MDB_stat db_stats;
+ if ((result = mdb_stat(txn, m_blocks, &db_stats)))
+ throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str()));
+ m_height = db_stats.ms_entries;
+ LOG_PRINT_L0("Total number of blocks: " << m_height);
+ LOG_PRINT_L1("block migration will update block_heights, block_info, and hf_versions...");
+
LOG_PRINT_L1("migrating block_heights:");
MDB_dbi o_heights;
unsigned int flags;
- result = mdb_txn_begin(m_env, NULL, 0, txn);
- if (result)
- throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
result = mdb_dbi_flags(txn, m_block_heights, &flags);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to retrieve block_heights flags: ", result).c_str()));
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index b0d8d9d0a..e7faf8cdc 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -247,7 +247,7 @@ public:
);
virtual void set_batch_transactions(bool batch_transactions);
- virtual void batch_start(uint64_t batch_num_blocks=0);
+ virtual bool batch_start(uint64_t batch_num_blocks=0);
virtual void batch_commit();
virtual void batch_stop();
virtual void batch_abort();
@@ -369,7 +369,6 @@ private:
MDB_dbi m_properties;
- uint64_t m_height;
uint64_t m_num_txs;
uint64_t m_num_outputs;
mutable uint64_t m_cum_size; // used in batch size estimation
diff --git a/src/blockchain_utilities/fake_core.h b/src/blockchain_utilities/fake_core.h
index ba1c2ed72..814139602 100644
--- a/src/blockchain_utilities/fake_core.h
+++ b/src/blockchain_utilities/fake_core.h
@@ -119,9 +119,9 @@ struct fake_core_db
return m_storage.get_db().add_block(blk, block_size, cumulative_difficulty, coins_generated, txs);
}
- void batch_start(uint64_t batch_num_blocks = 0)
+ bool batch_start(uint64_t batch_num_blocks = 0)
{
- m_storage.get_db().batch_start(batch_num_blocks);
+ return m_storage.get_db().batch_start(batch_num_blocks);
}
void batch_stop()
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/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 5f9d6937b..47fc26d39 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -375,6 +375,7 @@ bool Blockchain::init(BlockchainDB* db, const bool testnet, const cryptonote::te
LOG_PRINT_GREEN("Blockchain initialized. last block: " << m_db->height() - 1 << ", " << epee::misc_utils::get_time_interval_string(timestamp_diff) << " time ago, current difficulty: " << get_difficulty_for_next_block(), LOG_LEVEL_0);
m_db->block_txn_stop();
+ update_next_cumulative_size_limit();
return true;
}
//------------------------------------------------------------------
@@ -508,7 +509,7 @@ block Blockchain::pop_block_from_blockchain()
// that might not be always true. Unlikely though, and always relaying
// these again might cause a spike of traffic as many nodes re-relay
// all the transactions in a popped block when a reorg happens.
- bool r = m_tx_pool.add_tx(tx, tvc, true, true, version);
+ bool r = m_tx_pool.add_tx(tx, tvc, true, true, false, version);
if (!r)
{
LOG_ERROR("Error returning transaction to tx_pool");
@@ -532,6 +533,7 @@ bool Blockchain::reset_and_set_genesis_block(const block& b)
block_verification_context bvc = boost::value_initialized<block_verification_context>();
add_new_block(b, bvc);
+ update_next_cumulative_size_limit();
return bvc.m_added_to_main_chain && !bvc.m_verifivation_failed;
}
//------------------------------------------------------------------
@@ -1259,6 +1261,14 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
return false;
}
+ // this is a cheap test
+ if (!m_hardfork->check_for_height(b, block_height))
+ {
+ LOG_PRINT_L1("Block with id: " << id << std::endl << "has old version for height " << block_height);
+ bvc.m_verifivation_failed = true;
+ return false;
+ }
+
//block is not related with head of main chain
//first of all - look in alternative chains container
auto it_prev = m_alternative_chains.find(b.prev_id);
@@ -1778,7 +1788,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;
}
@@ -2188,7 +2198,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
@@ -2198,8 +2211,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;
@@ -2330,10 +2343,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr
CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type));
}
- // outPk
- CHECK_AND_ASSERT_MES(rv.outPk.size() == tx.vout.size(), false, "Bad outPk size");
- for (size_t n = 0; n < tx.rct_signatures.outPk.size(); ++n)
- rv.outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key);
+ // outPk was already done by handle_incoming_tx
return true;
}
@@ -2638,7 +2648,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
}
}
- if (!rct::verRctSimple(rv))
+ if (!rct::verRctSimple(rv, false))
{
LOG_PRINT_L1("Failed to check ringct signatures!");
return false;
@@ -2696,7 +2706,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
}
}
- if (!rct::verRct(rv))
+ if (!rct::verRct(rv, false))
{
LOG_PRINT_L1("Failed to check ringct signatures!");
return false;
@@ -2970,7 +2980,7 @@ void Blockchain::return_tx_to_pool(const std::vector<transaction> &txs)
// that might not be always true. Unlikely though, and always relaying
// these again might cause a spike of traffic as many nodes re-relay
// all the transactions in a popped block when a reorg happens.
- if (!m_tx_pool.add_tx(tx, tvc, true, true, version))
+ if (!m_tx_pool.add_tx(tx, tvc, true, true, false, version))
{
LOG_PRINT_L0("Failed to return taken transaction with hash: " << get_transaction_hash(tx) << " to tx_pool");
}
@@ -2987,9 +2997,9 @@ bool Blockchain::flush_txes_from_pool(const std::list<crypto::hash> &txids)
cryptonote::transaction tx;
size_t blob_size;
uint64_t fee;
- bool relayed;
+ bool relayed, do_not_relay;
LOG_PRINT_L1("Removing txid " << txid << " from the pool");
- if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed))
+ if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed, do_not_relay))
{
LOG_PRINT_L0("Failed to remove txid " << txid << " from the pool");
res = false;
@@ -3150,7 +3160,7 @@ leave:
transaction tx;
size_t blob_size = 0;
uint64_t fee = 0;
- bool relayed = false;
+ bool relayed = false, do_not_relay = false;
TIME_MEASURE_START(aa);
// XXX old code does not check whether tx exists
@@ -3167,7 +3177,7 @@ leave:
TIME_MEASURE_START(bb);
// get transaction with hash <tx_id> from tx_pool
- if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed))
+ if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed, do_not_relay))
{
LOG_PRINT_L1("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id);
bvc.m_verifivation_failed = true;
@@ -3378,9 +3388,10 @@ bool Blockchain::add_new_block(const block& bl_, block_verification_context& bvc
void Blockchain::check_against_checkpoints(const checkpoints& points, bool enforce)
{
const auto& pts = points.get_points();
+ bool stop_batch;
CRITICAL_REGION_LOCAL(m_blockchain_lock);
- m_db->batch_start();
+ stop_batch = m_db->batch_start();
for (const auto& pt : pts)
{
// if the checkpoint is for a block we don't have yet, move on
@@ -3404,7 +3415,8 @@ void Blockchain::check_against_checkpoints(const checkpoints& points, bool enfor
}
}
}
- m_db->batch_stop();
+ if (stop_batch)
+ m_db->batch_stop();
}
//------------------------------------------------------------------
// returns false if any of the checkpoints loading returns false.
@@ -3478,6 +3490,7 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync)
CRITICAL_REGION_LOCAL(m_blockchain_lock);
TIME_MEASURE_START(t1);
+ m_db->batch_stop();
if (m_sync_counter > 0)
{
if (force_sync)
@@ -3542,11 +3555,18 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e
{
LOG_PRINT_YELLOW("Blockchain::" << __func__, LOG_LEVEL_3);
TIME_MEASURE_START(prepare);
+ bool stop_batch;
CRITICAL_REGION_LOCAL(m_blockchain_lock);
if(blocks_entry.size() == 0)
return false;
+ while (!(stop_batch = m_db->batch_start(blocks_entry.size()))) {
+ m_blockchain_lock.unlock();
+ epee::misc_utils::sleep_no_w(1000);
+ m_blockchain_lock.lock();
+ }
+
if ((m_db->height() + blocks_entry.size()) < m_blocks_hash_check.size())
return true;
@@ -3951,12 +3971,12 @@ void Blockchain::load_compiled_in_block_hashes()
size_t blob_size;
uint64_t fee;
- bool relayed;
+ bool relayed, do_not_relay;
transaction pool_tx;
for(const transaction &tx : txs)
{
crypto::hash tx_hash = get_transaction_hash(tx);
- m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed);
+ m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed, do_not_relay);
}
}
}
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 4010d3d44..ede7ed748 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -49,6 +49,7 @@ using namespace epee;
#if defined(BERKELEY_DB)
#include "blockchain_db/berkeleydb/db_bdb.h"
#endif
+#include "ringct/rctSigs.h"
DISABLE_VS_WARNINGS(4355)
@@ -141,7 +142,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 +250,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 +303,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");
@@ -323,9 +313,9 @@ namespace cryptonote
LOG_PRINT_L0("Loading blockchain from folder " << folder.string() << " ...");
const std::string filename = folder.string();
- // temporarily default to fastest:async:1000
+ // default to fast:async:1
blockchain_db_sync_mode sync_mode = db_async;
- uint64_t blocks_per_sync = 1000;
+ uint64_t blocks_per_sync = 1;
try
{
@@ -338,12 +328,12 @@ namespace cryptonote
for(const auto &option : options)
LOG_PRINT_L0("option: " << option);
- // default to fast:async:1000
+ // default to fast:async:1
uint64_t DEFAULT_FLAGS = DBS_FAST_MODE;
if(options.size() == 0)
{
- // temporarily default to fastest:async:1000
+ // default to fast:async:1
db_flags = DEFAULT_FLAGS;
}
@@ -359,7 +349,10 @@ namespace cryptonote
else if(options[0] == "fast")
db_flags = DBS_FAST_MODE;
else if(options[0] == "fastest")
+ {
db_flags = DBS_FASTEST_MODE;
+ blocks_per_sync = 1000; // default to fastest:async:1000
+ }
else
db_flags = DEFAULT_FLAGS;
}
@@ -380,8 +373,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;
@@ -465,7 +456,7 @@ namespace cryptonote
return false;
}
//-----------------------------------------------------------------------------------------------
- bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed)
+ bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
{
tvc = boost::value_initialized<tx_verification_context>();
//want to process all transactions sequentially
@@ -507,6 +498,22 @@ namespace cryptonote
return false;
}
+ // resolve outPk references in rct txes
+ // outPk aren't the only thing that need resolving for a fully resolved tx,
+ // but outPk (1) are needed now to check range proof semantics, and
+ // (2) do not need access to the blockchain to find data
+ if (tx.version >= 2)
+ {
+ rct::rctSig &rv = tx.rct_signatures;
+ if (rv.outPk.size() != tx.vout.size())
+ {
+ LOG_PRINT_L1("WRONG TRANSACTION BLOB, Bad outPk size in tx " << tx_hash << ", rejected");
+ return false;
+ }
+ for (size_t n = 0; n < tx.rct_signatures.outPk.size(); ++n)
+ rv.outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key);
+ }
+
if(!check_tx_semantic(tx, keeped_by_block))
{
LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " semantic, rejected");
@@ -514,7 +521,7 @@ namespace cryptonote
return false;
}
- bool r = add_new_tx(tx, tx_hash, tx_prefixt_hash, tx_blob.size(), tvc, keeped_by_block, relayed);
+ bool r = add_new_tx(tx, tx_hash, tx_prefixt_hash, tx_blob.size(), tvc, keeped_by_block, relayed, do_not_relay);
if(tvc.m_verifivation_failed)
{LOG_PRINT_RED_L1("Transaction verification failed: " << tx_hash);}
else if(tvc.m_verifivation_impossible)
@@ -597,6 +604,33 @@ namespace cryptonote
return false;
}
+ if (tx.version >= 2)
+ {
+ const rct::rctSig &rv = tx.rct_signatures;
+ switch (rv.type) {
+ case rct::RCTTypeNull:
+ // coinbase should not come here, so we reject for all other types
+ LOG_PRINT_RED_L1("Unexpected Null rctSig type");
+ return false;
+ case rct::RCTTypeSimple:
+ if (!rct::verRctSimple(rv, true))
+ {
+ LOG_PRINT_RED_L1("rct signature semantics check failed");
+ return false;
+ }
+ break;
+ case rct::RCTTypeFull:
+ if (!rct::verRct(rv, true))
+ {
+ LOG_PRINT_RED_L1("rct signature semantics check failed");
+ return false;
+ }
+ break;
+ default:
+ LOG_PRINT_RED_L1("Unknown rct type: " << rv.type);
+ return false;
+ }
+ }
return true;
}
@@ -653,13 +687,13 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
- bool core::add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed)
+ bool core::add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
{
crypto::hash tx_hash = get_transaction_hash(tx);
crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx);
blobdata bl;
t_serializable_object_to_blob(tx, bl);
- return add_new_tx(tx, tx_hash, tx_prefix_hash, bl.size(), tvc, keeped_by_block, relayed);
+ return add_new_tx(tx, tx_hash, tx_prefix_hash, bl.size(), tvc, keeped_by_block, relayed, do_not_relay);
}
//-----------------------------------------------------------------------------------------------
size_t core::get_blockchain_total_transactions() const
@@ -667,7 +701,7 @@ namespace cryptonote
return m_blockchain_storage.get_total_transactions();
}
//-----------------------------------------------------------------------------------------------
- bool core::add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed)
+ bool core::add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
{
if(m_mempool.have_tx(tx_hash))
{
@@ -682,7 +716,7 @@ namespace cryptonote
}
uint8_t version = m_blockchain_storage.get_current_hard_fork_version();
- return m_mempool.add_tx(tx, tx_hash, blob_size, tvc, keeped_by_block, relayed, version);
+ return m_mempool.add_tx(tx, tx_hash, blob_size, tvc, keeped_by_block, relayed, do_not_relay, version);
}
//-----------------------------------------------------------------------------------------------
bool core::relay_txpool_transactions()
@@ -1010,6 +1044,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..fa67ff265 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -107,10 +107,11 @@ namespace cryptonote
* @param tvc metadata about the transaction's validity
* @param keeped_by_block if the transaction has been in a block
* @param relayed whether or not the transaction was relayed to us
+ * @param do_not_relay whether to prevent the transaction from being relayed
*
* @return true if the transaction made it to the transaction pool, otherwise false
*/
- bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed);
+ bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
/**
* @brief handles an incoming block
@@ -560,6 +561,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
@@ -635,9 +642,10 @@ namespace cryptonote
* @param tx_prefix_hash the transaction prefix' hash
* @param blob_size the size of the transaction
* @param relayed whether or not the transaction was relayed to us
+ * @param do_not_relay whether to prevent the transaction from being relayed
*
*/
- bool add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed);
+ bool add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
/**
* @brief add a new transaction to the transaction pool
@@ -648,12 +656,13 @@ namespace cryptonote
* @param tvc return-by-reference metadata about the transaction's verification process
* @param keeped_by_block whether or not the transaction has been in a block
* @param relayed whether or not the transaction was relayed to us
+ * @param do_not_relay whether to prevent the transaction from being relayed
*
* @return true if the transaction is already in the transaction pool,
* is already in a block on the Blockchain, or is successfully added
* to the transaction pool
*/
- bool add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed);
+ bool add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
/**
* @copydoc Blockchain::add_new_block
@@ -813,6 +822,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/hardfork.cpp b/src/cryptonote_core/hardfork.cpp
index c63ca36ef..8f9ff97d3 100644
--- a/src/cryptonote_core/hardfork.cpp
+++ b/src/cryptonote_core/hardfork.cpp
@@ -114,6 +114,19 @@ bool HardFork::check(const cryptonote::block &block) const
return do_check(::get_block_version(block), ::get_block_vote(block));
}
+bool HardFork::do_check_for_height(uint8_t block_version, uint8_t voting_version, uint64_t height) const
+{
+ int fork_index = get_voted_fork_index(height);
+ return block_version == heights[fork_index].version
+ && voting_version >= heights[fork_index].version;
+}
+
+bool HardFork::check_for_height(const cryptonote::block &block, uint64_t height) const
+{
+ CRITICAL_REGION_LOCAL(lock);
+ return do_check_for_height(::get_block_version(block), ::get_block_vote(block), height);
+}
+
bool HardFork::add(uint8_t block_version, uint8_t voting_version, uint64_t height)
{
CRITICAL_REGION_LOCAL(lock);
diff --git a/src/cryptonote_core/hardfork.h b/src/cryptonote_core/hardfork.h
index 33b958f9c..8d2edfa2a 100644
--- a/src/cryptonote_core/hardfork.h
+++ b/src/cryptonote_core/hardfork.h
@@ -114,6 +114,20 @@ namespace cryptonote
bool check(const cryptonote::block &block) const;
/**
+ * @brief same as check, but for a particular height, rather than the top
+ *
+ * NOTE: this does not play well with voting, and relies on voting to be
+ * disabled (that is, forks happen on the scheduled date, whether or not
+ * enough blocks have voted for the fork).
+ *
+ * returns true if no error, false otherwise
+ *
+ * @param block the new block
+ * @param height which height to check for
+ */
+ bool check_for_height(const cryptonote::block &block, uint64_t height) const;
+
+ /**
* @brief add a new block
*
* returns true if no error, false otherwise
@@ -211,6 +225,7 @@ namespace cryptonote
uint8_t get_block_version(uint64_t height) const;
bool do_check(uint8_t block_version, uint8_t voting_version) const;
+ bool do_check_for_height(uint8_t block_version, uint8_t voting_version, uint64_t height) const;
int get_voted_fork_index(uint64_t height) const;
uint8_t get_effective_version(uint8_t voting_version) const;
bool add(uint8_t block_version, uint8_t voting_version, uint64_t height);
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index 59ac534fe..78d75f41f 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -83,7 +83,7 @@ namespace cryptonote
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version)
+ bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version)
{
PERF_TIMER(add_tx);
if (tx.version == 0)
@@ -177,6 +177,8 @@ namespace cryptonote
return false;
}
+ time_t receive_time = time(nullptr);
+
crypto::hash max_used_block_id = null_hash;
uint64_t max_used_block_height = 0;
tx_details txd;
@@ -198,9 +200,10 @@ namespace cryptonote
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.receive_time = receive_time;
txd_p.first->second.last_relayed_time = time(NULL);
txd_p.first->second.relayed = relayed;
+ txd_p.first->second.do_not_relay = do_not_relay;
tvc.m_verifivation_impossible = true;
tvc.m_added_to_pool = true;
}else
@@ -221,12 +224,13 @@ namespace cryptonote
txd_p.first->second.max_used_block_height = max_used_block_height;
txd_p.first->second.last_failed_height = 0;
txd_p.first->second.last_failed_id = null_hash;
- txd_p.first->second.receive_time = time(nullptr);
+ txd_p.first->second.receive_time = receive_time;
txd_p.first->second.last_relayed_time = time(NULL);
txd_p.first->second.relayed = relayed;
+ txd_p.first->second.do_not_relay = do_not_relay;
tvc.m_added_to_pool = true;
- if(txd_p.first->second.fee > 0)
+ if(txd_p.first->second.fee > 0 && !do_not_relay)
tvc.m_should_be_relayed = true;
}
@@ -246,17 +250,17 @@ namespace cryptonote
tvc.m_verifivation_failed = false;
- m_txs_by_fee.emplace((double)blob_size / fee, id);
+ m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>((double)blob_size / fee, receive_time), id);
return true;
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::add_tx(const transaction &tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, uint8_t version)
+ bool tx_memory_pool::add_tx(const transaction &tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay, uint8_t version)
{
crypto::hash h = null_hash;
size_t blob_size = 0;
get_transaction_hash(tx, h, blob_size);
- return add_tx(tx, h, blob_size, tvc, keeped_by_block, relayed, version);
+ return add_tx(tx, h, blob_size, tvc, keeped_by_block, relayed, do_not_relay, version);
}
//---------------------------------------------------------------------------------
//FIXME: Can return early before removal of all of the key images.
@@ -292,7 +296,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed)
+ bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay)
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
auto it = m_transactions.find(id);
@@ -301,16 +305,17 @@ namespace cryptonote
auto sorted_it = find_tx_in_sorted_container(id);
- if (sorted_it == m_txs_by_fee.end())
+ if (sorted_it == m_txs_by_fee_and_receive_time.end())
return false;
tx = it->second.tx;
blob_size = it->second.blob_size;
fee = it->second.fee;
relayed = it->second.relayed;
+ do_not_relay = it->second.do_not_relay;
remove_transaction_keyimages(it->second.tx);
m_transactions.erase(it);
- m_txs_by_fee.erase(sorted_it);
+ m_txs_by_fee_and_receive_time.erase(sorted_it);
return true;
}
//---------------------------------------------------------------------------------
@@ -321,7 +326,7 @@ namespace cryptonote
//---------------------------------------------------------------------------------
sorted_tx_container::iterator tx_memory_pool::find_tx_in_sorted_container(const crypto::hash& id) const
{
- return std::find_if( m_txs_by_fee.begin(), m_txs_by_fee.end()
+ return std::find_if( m_txs_by_fee_and_receive_time.begin(), m_txs_by_fee_and_receive_time.end()
, [&](const sorted_tx_container::value_type& a){
return a.second == id;
}
@@ -342,13 +347,13 @@ namespace cryptonote
LOG_PRINT_L1("Tx " << it->first << " removed from tx pool due to outdated, age: " << tx_age );
remove_transaction_keyimages(it->second.tx);
auto sorted_it = find_tx_in_sorted_container(it->first);
- if (sorted_it == m_txs_by_fee.end())
+ if (sorted_it == m_txs_by_fee_and_receive_time.end())
{
LOG_PRINT_L1("Removing tx " << it->first << " from tx pool, but it was not found in the sorted txs container!");
}
else
{
- m_txs_by_fee.erase(sorted_it);
+ m_txs_by_fee_and_receive_time.erase(sorted_it);
}
m_timed_out_transactions.insert(it->first);
auto pit = it++;
@@ -367,7 +372,7 @@ namespace cryptonote
for(auto it = m_transactions.begin(); it!= m_transactions.end();)
{
// 0 fee transactions are never relayed
- if(it->second.fee > 0 && now - it->second.last_relayed_time > get_relay_delay(now, it->second.receive_time))
+ if(it->second.fee > 0 && !it->second.do_not_relay && now - it->second.last_relayed_time > get_relay_delay(now, it->second.receive_time))
{
// if the tx is older than half the max lifetime, we don't re-relay it, to avoid a problem
// mentioned by smooth where nodes would flush txes at slightly different times, causing
@@ -431,6 +436,7 @@ namespace cryptonote
txi.receive_time = txd.receive_time;
txi.relayed = txd.relayed;
txi.last_relayed_time = txd.last_relayed_time;
+ txi.do_not_relay = txd.do_not_relay;
tx_infos.push_back(txi);
}
@@ -608,9 +614,9 @@ namespace cryptonote
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())
+ LOG_PRINT_L2("Filling block template, median size " << median_size << ", " << m_txs_by_fee_and_receive_time.size() << " txes in the pool");
+ auto sorted_it = m_txs_by_fee_and_receive_time.begin();
+ while (sorted_it != m_txs_by_fee_and_receive_time.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));
@@ -675,13 +681,13 @@ namespace cryptonote
LOG_PRINT_L1("Transaction " << get_transaction_hash(it->second.tx) << " is too big (" << it->second.blob_size << " bytes), removing it from pool");
remove_transaction_keyimages(it->second.tx);
auto sorted_it = find_tx_in_sorted_container(it->first);
- if (sorted_it == m_txs_by_fee.end())
+ if (sorted_it == m_txs_by_fee_and_receive_time.end())
{
LOG_PRINT_L1("Removing tx " << it->first << " from tx pool, but it was not found in the sorted txs container!");
}
else
{
- m_txs_by_fee.erase(sorted_it);
+ m_txs_by_fee_and_receive_time.erase(sorted_it);
}
auto pit = it++;
m_transactions.erase(pit);
@@ -712,14 +718,14 @@ namespace cryptonote
LOG_ERROR("Failed to load memory pool from file " << state_file_path);
m_transactions.clear();
- m_txs_by_fee.clear();
+ m_txs_by_fee_and_receive_time.clear();
m_spent_key_images.clear();
}
// no need to store queue of sorted transactions, as it's easy to generate.
for (const auto& tx : m_transactions)
{
- m_txs_by_fee.emplace((double)tx.second.blob_size / tx.second.fee, tx.first);
+ m_txs_by_fee_and_receive_time.emplace(std::pair<double, time_t>((double)tx.second.blob_size / tx.second.fee, tx.second.receive_time), tx.first);
}
// Ignore deserialization error
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index 32a05b0f4..2712f75bb 100644
--- a/src/cryptonote_core/tx_pool.h
+++ b/src/cryptonote_core/tx_pool.h
@@ -54,23 +54,25 @@ namespace cryptonote
/************************************************************************/
//! pair of <transaction fee, transaction hash> for organization
- typedef std::pair<double, crypto::hash> tx_by_fee_entry;
+ typedef std::pair<std::pair<double, std::time_t>, crypto::hash> tx_by_fee_and_receive_time_entry;
class txCompare
{
public:
- bool operator()(const tx_by_fee_entry& a, const tx_by_fee_entry& b)
+ bool operator()(const tx_by_fee_and_receive_time_entry& a, const tx_by_fee_and_receive_time_entry& b)
{
// sort by greatest first, not least
- if (a.first > b.first) return true;
- else if (a.first < b.first) return false;
+ if (a.first.first > b.first.first) return true;
+ else if (a.first.first < b.first.first) return false;
+ else if (a.first.second < b.first.second) return true;
+ else if (a.first.second > b.first.second) return false;
else if (a.second != b.second) return true;
else return false;
}
};
//! container for sorting transactions by fee per unit size
- typedef std::set<tx_by_fee_entry, txCompare> sorted_tx_container;
+ typedef std::set<tx_by_fee_and_receive_time_entry, txCompare> sorted_tx_container;
/**
* @brief Transaction pool, handles transactions which are not part of a block
@@ -103,7 +105,7 @@ namespace cryptonote
* @param id the transaction's hash
* @param blob_size the transaction's size
*/
- bool add_tx(const transaction &tx, const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version);
+ bool add_tx(const transaction &tx, const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version);
/**
* @brief add a transaction to the transaction pool
@@ -117,11 +119,12 @@ namespace cryptonote
* @param tvc return-by-reference status about the transaction verification
* @param kept_by_block has this transaction been in a block?
* @param relayed was this transaction from the network or a local client?
+ * @param do_not_relay to avoid relaying the transaction to the network
* @param version the version used to create the transaction
*
* @return true if the transaction passes validations, otherwise false
*/
- bool add_tx(const transaction &tx, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version);
+ bool add_tx(const transaction &tx, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version);
/**
* @brief takes a transaction with the given hash from the pool
@@ -131,10 +134,11 @@ namespace cryptonote
* @param blob_size return-by-reference the transaction's size
* @param fee the transaction fee
* @param relayed return-by-reference was transaction relayed to us by the network?
+ * @param do_not_relay return-by-reference is transaction not to be relayed to the network?
*
* @return true unless the transaction cannot be found in the pool
*/
- bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed);
+ bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay);
/**
* @brief checks if the pool has a transaction with the given hash
@@ -303,7 +307,7 @@ namespace cryptonote
#define CURRENT_MEMPOOL_ARCHIVE_VER 11
-#define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 11
+#define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 12
/**
* @brief serialize the transaction pool to/from disk
@@ -362,6 +366,7 @@ namespace cryptonote
time_t last_relayed_time; //!< the last time the transaction was relayed to the network
bool relayed; //!< whether or not the transaction has been relayed to the network
+ bool do_not_relay; //!< to avoid relay this transaction to the network
};
private:
@@ -473,7 +478,8 @@ private:
epee::math_helper::once_a_time_seconds<30> m_remove_stuck_tx_interval;
//TODO: look into doing this better
- sorted_tx_container m_txs_by_fee; //!< container for transactions organized by fee per size
+ //!< container for transactions organized by fee per size and receive time
+ sorted_tx_container m_txs_by_fee_and_receive_time;
/**
* @brief get an iterator to a transaction in the sorted container
@@ -515,6 +521,9 @@ namespace boost
if (version < 11)
return;
ar & td.kept_by_block;
+ if (version < 12)
+ return;
+ ar & td.do_not_relay;
}
}
}
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index b13c1f437..54f1c849e 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -328,7 +328,7 @@ namespace cryptonote
for(auto tx_blob_it = arg.b.txs.begin(); tx_blob_it!=arg.b.txs.end();tx_blob_it++)
{
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
- m_core.handle_incoming_tx(*tx_blob_it, tvc, true, true);
+ m_core.handle_incoming_tx(*tx_blob_it, tvc, true, true, false);
if(tvc.m_verifivation_failed)
{
LOG_PRINT_CCONTEXT_L1("Block verification failed: transaction verification failed, dropping connection");
@@ -478,7 +478,7 @@ namespace cryptonote
if(!m_core.get_pool_transaction(tx_hash, tx))
{
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
- if(!m_core.handle_incoming_tx(tx_blob, tvc, true, true) || tvc.m_verifivation_failed)
+ if(!m_core.handle_incoming_tx(tx_blob, tvc, true, true, false) || tvc.m_verifivation_failed)
{
LOG_PRINT_CCONTEXT_L1("Block verification failed: transaction verification failed, dropping connection");
m_p2p->drop_connection(context);
@@ -682,7 +682,7 @@ namespace cryptonote
for(auto tx_blob_it = arg.txs.begin(); tx_blob_it!=arg.txs.end();)
{
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
- m_core.handle_incoming_tx(*tx_blob_it, tvc, false, true);
+ m_core.handle_incoming_tx(*tx_blob_it, tvc, false, true, false);
if(tvc.m_verifivation_failed)
{
LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection");
@@ -874,7 +874,7 @@ namespace cryptonote
BOOST_FOREACH(auto& tx_blob, block_entry.txs)
{
tx_verification_context tvc = AUTO_VAL_INIT(tvc);
- m_core.handle_incoming_tx(tx_blob, tvc, true, true);
+ m_core.handle_incoming_tx(tx_blob, tvc, true, true, false);
if(tvc.m_verifivation_failed)
{
LOG_ERROR_CCONTEXT("transaction verification failed on NOTIFY_RESPONSE_GET_OBJECTS, \r\ntx_id = "
diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp
index 7381dd06f..9d28cd41e 100644
--- a/src/daemon/command_parser_executor.cpp
+++ b/src/daemon/command_parser_executor.cpp
@@ -511,4 +511,22 @@ bool t_command_parser_executor::alt_chain_info(const std::vector<std::string>& a
return m_executor.alt_chain_info();
}
+bool t_command_parser_executor::print_blockchain_dynamic_stats(const std::vector<std::string>& args)
+{
+ if(args.size() != 1)
+ {
+ std::cout << "Exactly one parameter is needed" << std::endl;
+ return false;
+ }
+
+ uint64_t nblocks = 0;
+ if(!epee::string_tools::get_xtype_from_string(nblocks, args[0]) || nblocks == 0)
+ {
+ std::cout << "wrong number of blocks" << std::endl;
+ return false;
+ }
+
+ return m_executor.print_blockchain_dynamic_stats(nblocks);
+}
+
} // namespace daemonize
diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h
index cc929db00..7763ebed0 100644
--- a/src/daemon/command_parser_executor.h
+++ b/src/daemon/command_parser_executor.h
@@ -121,6 +121,8 @@ public:
bool print_coinbase_tx_sum(const std::vector<std::string>& args);
bool alt_chain_info(const std::vector<std::string>& args);
+
+ bool print_blockchain_dynamic_stats(const std::vector<std::string>& args);
};
} // namespace daemonize
diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp
index 88c49d111..086478a47 100644
--- a/src/daemon/command_server.cpp
+++ b/src/daemon/command_server.cpp
@@ -230,6 +230,11 @@ t_command_server::t_command_server(
, std::bind(&t_command_parser_executor::alt_chain_info, &m_parser, p::_1)
, "Print information about alternative chains"
);
+ m_command_lookup.set_handler(
+ "bc_dyn_stats"
+ , std::bind(&t_command_parser_executor::print_blockchain_dynamic_stats, &m_parser, p::_1)
+ , "Print information about current blockchain dynamic state"
+ );
}
bool t_command_server::process_command_str(const std::string& cmd)
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index 4d415b4c2..c7a122d00 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;
@@ -738,6 +743,7 @@ bool t_rpc_command_executor::print_transaction_pool_long() {
<< "fee: " << cryptonote::print_money(tx_info.fee) << std::endl
<< "receive_time: " << tx_info.receive_time << " (" << get_human_time_ago(tx_info.receive_time, now) << ")" << std::endl
<< "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl
+ << "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
@@ -818,6 +824,7 @@ bool t_rpc_command_executor::print_transaction_pool_short() {
<< "fee: " << cryptonote::print_money(tx_info.fee) << std::endl
<< "receive_time: " << tx_info.receive_time << " (" << get_human_time_ago(tx_info.receive_time, now) << ")" << std::endl
<< "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl
+ << "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
@@ -1401,5 +1408,108 @@ bool t_rpc_command_executor::alt_chain_info()
return true;
}
+bool t_rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks)
+{
+ cryptonote::COMMAND_RPC_GET_INFO::request ireq;
+ cryptonote::COMMAND_RPC_GET_INFO::response ires;
+ cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request bhreq;
+ cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response bhres;
+ cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request fereq;
+ cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response feres;
+ epee::json_rpc::error error_resp;
+
+ std::string fail_message = "Problem fetching info";
+
+ if (m_is_rpc)
+ {
+ if (!m_rpc_client->rpc_request(ireq, ires, "/getinfo", fail_message.c_str()))
+ {
+ return true;
+ }
+ if (!m_rpc_client->rpc_request(fereq, feres, "/get_fee_estimate", fail_message.c_str()))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (!m_rpc_server->on_get_info(ireq, ires) || ires.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << fail_message.c_str();
+ return true;
+ }
+ if (!m_rpc_server->on_get_per_kb_fee_estimate(fereq, feres, error_resp) || feres.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << fail_message.c_str();
+ return true;
+ }
+ }
+
+ tools::msg_writer() << "Height: " << ires.height << ", diff " << ires.difficulty << ", cum. diff " << ires.cumulative_difficulty
+ << ", target " << ires.target << " sec" << ", dyn fee " << cryptonote::print_money(feres.fee) << "/kB";
+
+ if (nblocks > 0)
+ {
+ if (nblocks > ires.height)
+ nblocks = ires.height;
+
+ bhreq.start_height = ires.height - nblocks;
+ bhreq.end_height = ires.height - 1;
+ if (m_is_rpc)
+ {
+ if (!m_rpc_client->rpc_request(bhreq, bhres, "/getblockheadersrange", fail_message.c_str()))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (!m_rpc_server->on_get_block_headers_range(bhreq, bhres, error_resp) || bhres.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << fail_message.c_str();
+ return true;
+ }
+ }
+
+ double avgdiff = 0;
+ double avgnumtxes = 0;
+ double avgreward = 0;
+ std::vector<uint64_t> sizes;
+ sizes.reserve(nblocks);
+ std::vector<unsigned> major_versions(256, 0), minor_versions(256, 0);
+ for (const auto &bhr: bhres.headers)
+ {
+ avgdiff += bhr.difficulty;
+ avgnumtxes += bhr.num_txes;
+ avgreward += bhr.reward;
+ sizes.push_back(bhr.block_size);
+ static_assert(sizeof(bhr.major_version) == 1, "major_version expected to be uint8_t");
+ static_assert(sizeof(bhr.minor_version) == 1, "major_version expected to be uint8_t");
+ major_versions[(unsigned)bhr.major_version]++;
+ minor_versions[(unsigned)bhr.minor_version]++;
+ }
+ avgdiff /= nblocks;
+ avgnumtxes /= nblocks;
+ avgreward /= nblocks;
+ uint64_t median_block_size = epee::misc_utils::median(sizes);
+ tools::msg_writer() << "Last " << nblocks << ": avg. diff " << (uint64_t)avgdiff << ", avg num txes " << avgnumtxes
+ << ", avg. reward " << cryptonote::print_money(avgreward) << ", median block size " << median_block_size;
+
+ unsigned int max_major = 256, max_minor = 256;
+ while (max_major > 0 && !major_versions[--max_major]);
+ while (max_minor > 0 && !minor_versions[--max_minor]);
+ std::string s = "";
+ for (unsigned n = 0; n <= max_major; ++n)
+ if (major_versions[n])
+ s += (s.empty() ? "" : ", ") + boost::lexical_cast<std::string>(major_versions[n]) + std::string(" v") + boost::lexical_cast<std::string>(n);
+ tools::msg_writer() << "Block versions: " << s;
+ s = "";
+ for (unsigned n = 0; n <= max_minor; ++n)
+ if (minor_versions[n])
+ s += (s.empty() ? "" : ", ") + boost::lexical_cast<std::string>(minor_versions[n]) + std::string(" v") + boost::lexical_cast<std::string>(n);
+ tools::msg_writer() << "Voting for: " << s;
+ }
+ return true;
+}
}// namespace daemonize
diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h
index a6c712c04..af283c1f1 100644
--- a/src/daemon/rpc_command_executor.h
+++ b/src/daemon/rpc_command_executor.h
@@ -139,6 +139,8 @@ public:
bool print_coinbase_tx_sum(uint64_t height, uint64_t count);
bool alt_chain_info();
+
+ bool print_blockchain_dynamic_stats(uint64_t nblocks);
};
} // namespace daemonize
diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp
index 33d36d2d0..f0e254ba4 100644
--- a/src/mnemonics/electrum-words.cpp
+++ b/src/mnemonics/electrum-words.cpp
@@ -64,12 +64,15 @@
namespace
{
+ uint32_t create_checksum_index(const std::vector<std::string> &word_list,
+ uint32_t unique_prefix_length);
+ bool checksum_test(std::vector<std::string> seed, uint32_t unique_prefix_length);
/*!
* \brief Finds the word list that contains the seed words and puts the indices
* where matches occured in matched_indices.
* \param seed List of words to match.
- * \param has_checksum If word list passed checksum test, we need to only do a prefix check.
+ * \param has_checksum The seed has a checksum word (maybe not checked).
* \param matched_indices The indices where the seed words were found are added to this.
* \param language Language instance pointer to write to after it is found.
* \return true if all the words were present in some language false if not.
@@ -88,6 +91,7 @@ namespace
Language::Singleton<Language::Russian>::instance(),
Language::Singleton<Language::OldEnglish>::instance()
});
+ Language::Base *fallback = NULL;
// Iterate through all the languages and find a match
for (std::vector<Language::Base*>::iterator it1 = language_instances.begin();
@@ -126,12 +130,33 @@ namespace
}
if (full_match)
{
+ // if we were using prefix only, and we have a checksum, check it now
+ // to avoid false positives due to prefix set being too common
+ if (has_checksum)
+ if (!checksum_test(seed, (*it1)->get_unique_prefix_length()))
+ {
+ fallback = *it1;
+ full_match = false;
+ }
+ }
+ if (full_match)
+ {
*language = *it1;
return true;
}
// Some didn't match. Clear the index array.
matched_indices.clear();
}
+
+ // if we get there, we've not found a good match, but we might have a fallback,
+ // if we detected a match which did not fit the checksum, which might be a badly
+ // typed/transcribed seed in the right language
+ if (fallback)
+ {
+ *language = fallback;
+ return true;
+ }
+
return false;
}
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index 4f8782cdf..1e4f1e852 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -710,45 +710,56 @@ namespace rct {
//decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1)
// uses the attached ecdh info to find the amounts represented by each output commitment
// must know the destination private key to find the correct amount, else will return a random number
- bool verRct(const rctSig & rv) {
+ bool verRct(const rctSig & rv, bool semantics) {
PERF_TIMER(verRct);
CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull, false, "verRct called on non-full rctSig");
- CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs");
- CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo");
- CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "full rctSig has not one MG");
+ if (semantics)
+ {
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs");
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo");
+ CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "full rctSig has not one MG");
+ }
+ else
+ {
+ // semantics check is early, we don't have the MGs resolved yet
+ }
// some rct ops can throw
try
{
- std::deque<bool> results(rv.outPk.size(), false);
- tools::thread_group threadpool(tools::thread_group::optimal_with_max(rv.outPk.size()));
+ if (semantics) {
+ std::deque<bool> results(rv.outPk.size(), false);
+ tools::thread_group threadpool(tools::thread_group::optimal_with_max(rv.outPk.size()));
+
+ tools::task_region(threadpool, [&] (tools::task_region_handle& region) {
+ DP("range proofs verified?");
+ for (size_t i = 0; i < rv.outPk.size(); i++) {
+ region.run([&, i] {
+ results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
+ });
+ }
+ });
- tools::task_region(threadpool, [&] (tools::task_region_handle& region) {
- DP("range proofs verified?");
- for (size_t i = 0; i < rv.outPk.size(); i++) {
- region.run([&, i] {
- results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
- });
+ for (size_t i = 0; i < rv.outPk.size(); ++i) {
+ if (!results[i]) {
+ LOG_PRINT_L1("Range proof verified failed for output " << i);
+ return false;
+ }
}
- });
+ }
- for (size_t i = 0; i < rv.outPk.size(); ++i) {
- if (!results[i]) {
- LOG_PRINT_L1("Range proof verified failed for output " << i);
+ if (!semantics) {
+ //compute txn fee
+ key txnFeeKey = scalarmultH(d2h(rv.txnFee));
+ bool mgVerd = verRctMG(rv.p.MGs[0], rv.mixRing, rv.outPk, txnFeeKey, get_pre_mlsag_hash(rv));
+ DP("mg sig verified?");
+ DP(mgVerd);
+ if (!mgVerd) {
+ LOG_PRINT_L1("MG signature verification failed");
return false;
}
}
- //compute txn fee
- key txnFeeKey = scalarmultH(d2h(rv.txnFee));
- bool mgVerd = verRctMG(rv.p.MGs[0], rv.mixRing, rv.outPk, txnFeeKey, get_pre_mlsag_hash(rv));
- DP("mg sig verified?");
- DP(mgVerd);
- if (!mgVerd) {
- LOG_PRINT_L1("MG signature verification failed");
- return false;
- }
-
return true;
}
catch(...)
@@ -759,76 +770,86 @@ namespace rct {
//ver RingCT simple
//assumes only post-rct style inputs (at least for max anonymity)
- bool verRctSimple(const rctSig & rv) {
+ bool verRctSimple(const rctSig & rv, bool semantics) {
try
{
PERF_TIMER(verRctSimple);
CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple, false, "verRctSimple called on non simple rctSig");
- CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs");
- CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo");
- CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.pseudoOuts and rv.p.MGs");
- CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing");
+ if (semantics)
+ {
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs");
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo");
+ CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.pseudoOuts and rv.p.MGs");
+ }
+ else
+ {
+ // semantics check is early, and mixRing/MGs aren't resolved yet
+ CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing");
+ }
const size_t threads = std::max(rv.outPk.size(), rv.mixRing.size());
std::deque<bool> results(threads);
tools::thread_group threadpool(tools::thread_group::optimal_with_max(threads));
- results.clear();
- results.resize(rv.outPk.size());
- tools::task_region(threadpool, [&] (tools::task_region_handle& region) {
- for (size_t i = 0; i < rv.outPk.size(); i++) {
- region.run([&, i] {
- results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
- });
- }
- });
+ if (semantics) {
+ results.clear();
+ results.resize(rv.outPk.size());
+ tools::task_region(threadpool, [&] (tools::task_region_handle& region) {
+ for (size_t i = 0; i < rv.outPk.size(); i++) {
+ region.run([&, i] {
+ results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
+ });
+ }
+ });
- for (size_t i = 0; i < results.size(); ++i) {
- if (!results[i]) {
- LOG_PRINT_L1("Range proof verified failed for output " << i);
- return false;
+ for (size_t i = 0; i < results.size(); ++i) {
+ if (!results[i]) {
+ LOG_PRINT_L1("Range proof verified failed for output " << i);
+ return false;
+ }
}
- }
-
- key sumOutpks = identity();
- for (size_t i = 0; i < rv.outPk.size(); i++) {
- addKeys(sumOutpks, sumOutpks, rv.outPk[i].mask);
- }
- DP(sumOutpks);
- key txnFeeKey = scalarmultH(d2h(rv.txnFee));
- addKeys(sumOutpks, txnFeeKey, sumOutpks);
- key message = get_pre_mlsag_hash(rv);
+ key sumOutpks = identity();
+ for (size_t i = 0; i < rv.outPk.size(); i++) {
+ addKeys(sumOutpks, sumOutpks, rv.outPk[i].mask);
+ }
+ DP(sumOutpks);
+ key txnFeeKey = scalarmultH(d2h(rv.txnFee));
+ addKeys(sumOutpks, txnFeeKey, sumOutpks);
- results.clear();
- results.resize(rv.mixRing.size());
- tools::task_region(threadpool, [&] (tools::task_region_handle& region) {
- for (size_t i = 0 ; i < rv.mixRing.size() ; i++) {
- region.run([&, i] {
- results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], rv.pseudoOuts[i]);
- });
+ key sumPseudoOuts = identity();
+ for (size_t i = 0 ; i < rv.pseudoOuts.size() ; i++) {
+ addKeys(sumPseudoOuts, sumPseudoOuts, rv.pseudoOuts[i]);
}
- });
+ DP(sumPseudoOuts);
- for (size_t i = 0; i < results.size(); ++i) {
- if (!results[i]) {
- LOG_PRINT_L1("verRctMGSimple failed for input " << i);
- return false;
+ //check pseudoOuts vs Outs..
+ if (!equalKeys(sumPseudoOuts, sumOutpks)) {
+ LOG_PRINT_L1("Sum check failed");
+ return false;
}
}
+ else {
+ const key message = get_pre_mlsag_hash(rv);
- key sumPseudoOuts = identity();
- for (size_t i = 0 ; i < rv.mixRing.size() ; i++) {
- addKeys(sumPseudoOuts, sumPseudoOuts, rv.pseudoOuts[i]);
- }
- DP(sumPseudoOuts);
-
- //check pseudoOuts vs Outs..
- if (!equalKeys(sumPseudoOuts, sumOutpks)) {
- LOG_PRINT_L1("Sum check failed");
- return false;
+ results.clear();
+ results.resize(rv.mixRing.size());
+ tools::task_region(threadpool, [&] (tools::task_region_handle& region) {
+ for (size_t i = 0 ; i < rv.mixRing.size() ; i++) {
+ region.run([&, i] {
+ results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], rv.pseudoOuts[i]);
+ });
+ }
+ });
+
+ for (size_t i = 0; i < results.size(); ++i) {
+ if (!results[i]) {
+ LOG_PRINT_L1("verRctMGSimple failed for input " << i);
+ return false;
+ }
+ }
}
return true;
diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h
index 1fe4aa074..ca40ddd85 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -126,8 +126,10 @@ namespace rct {
rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin);
rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin);
rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk);
- bool verRct(const rctSig & rv);
- bool verRctSimple(const rctSig & rv);
+ bool verRct(const rctSig & rv, bool semantics);
+ static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); }
+ bool verRctSimple(const rctSig & rv, bool semantics);
+ static inline bool verRctSimple(const rctSig & rv) { return verRctSimple(rv, true) && verRctSimple(rv, false); }
xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask);
xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i);
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask);
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 6ca01ed2c..0cec3c26e 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -145,6 +145,7 @@ namespace cryptonote
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;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -300,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;
@@ -525,7 +528,7 @@ namespace cryptonote
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
tx_verification_context tvc = AUTO_VAL_INIT(tvc);
- if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false) || tvc.m_verifivation_failed)
+ if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false, req.do_not_relay) || tvc.m_verifivation_failed)
{
if (tvc.m_verifivation_failed)
{
@@ -555,7 +558,7 @@ namespace cryptonote
return true;
}
- if(!tvc.m_should_be_relayed || req.do_not_relay)
+ if(!tvc.m_should_be_relayed)
{
LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed");
res.reason = "Not relayed";
@@ -893,6 +896,7 @@ namespace cryptonote
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);
+ response.num_txes = blk.tx_hashes.size();
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -1165,6 +1169,7 @@ namespace cryptonote
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 c08e43066..c1dc9ed6c 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 3
+#define CORE_RPC_VERSION_MINOR 5
#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()
};
@@ -513,6 +521,7 @@ namespace cryptonote
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)
@@ -531,6 +540,7 @@ namespace cryptonote
KV_SERIALIZE(top_block_hash)
KV_SERIALIZE(cumulative_difficulty)
KV_SERIALIZE(block_size_limit)
+ KV_SERIALIZE(start_time)
END_KV_SERIALIZE_MAP()
};
};
@@ -696,6 +706,7 @@ namespace cryptonote
difficulty_type difficulty;
uint64_t reward;
uint64_t block_size;
+ uint64_t num_txes;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(major_version)
@@ -710,6 +721,7 @@ namespace cryptonote
KV_SERIALIZE(difficulty)
KV_SERIALIZE(reward)
KV_SERIALIZE(block_size)
+ KV_SERIALIZE(num_txes)
END_KV_SERIALIZE_MAP()
};
@@ -910,6 +922,7 @@ namespace cryptonote
uint64_t receive_time;
bool relayed;
uint64_t last_relayed_time;
+ bool do_not_relay;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(id_hash)
@@ -924,6 +937,7 @@ namespace cryptonote
KV_SERIALIZE(receive_time)
KV_SERIALIZE(relayed)
KV_SERIALIZE(last_relayed_time)
+ KV_SERIALIZE(do_not_relay)
END_KV_SERIALIZE_MAP()
};
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index c3637cb5f..3412617d4 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -72,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() \
@@ -371,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())
@@ -570,7 +580,7 @@ simple_wallet::simple_wallet()
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>"));
@@ -596,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();
@@ -632,6 +643,19 @@ bool simple_wallet::set_variable(const std::vector<std::string> &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;
+ }
+ }
else if (args[0] == "store-tx-info")
{
if (args.size() <= 1)
@@ -1119,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() << ". " <<
@@ -1130,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;
@@ -1542,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" <<
@@ -1865,6 +1896,108 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
+bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr)
+{
+ 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 (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)
+ {
+ 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("output key's originating block height shouldn't be higher than the blockchain height");
+ return false;
+ }
+ }
+ 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)
+ {
+ uint64_t pos = (res.outs[j].height * resolution) / blockchain_height;
+ ring_str[pos] = 'o';
+ }
+ 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)
+ {
+ 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;
+ }
+ }
+ 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_)
{
if (!try_connect_to_daemon())
@@ -2075,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())
@@ -2503,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))
@@ -2676,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;
}
@@ -3765,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;
@@ -3807,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)
{
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 753bca74d..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>());
@@ -160,11 +161,12 @@ namespace cryptonote
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 print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr);
/*!
* \brief Prints the seed with a nice message
@@ -184,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 b878491ce..e397dac04 100644
--- a/src/wallet/api/address_book.cpp
+++ b/src/wallet/api/address_book.cpp
@@ -33,6 +33,7 @@
#include "wallet.h"
#include "crypto/hash.h"
#include "wallet/wallet2.h"
+#include "common_defines.h"
#include <vector>
@@ -43,30 +44,50 @@ AddressBook::~AddressBook() {}
AddressBookImpl::AddressBookImpl(WalletImpl *wallet)
: m_wallet(wallet) {}
-bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &payment_id, const std::string &description)
+bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &payment_id_str, const std::string &description)
{
- LOG_PRINT_L2("Adding row");
-
clearStatus();
cryptonote::account_public_address addr;
- bool has_payment_id;
+ bool has_short_pid;
crypto::hash8 payment_id_short;
- if(!cryptonote::get_account_integrated_address_from_str(addr, has_payment_id, payment_id_short, m_wallet->m_wallet->testnet(), dst_addr)) {
- m_errorString = "Invalid destination address";
+ if(!cryptonote::get_account_integrated_address_from_str(addr, has_short_pid, payment_id_short, m_wallet->m_wallet->testnet(), dst_addr)) {
+ m_errorString = tr("Invalid destination address");
m_errorCode = Invalid_Address;
return false;
}
- crypto::hash pid32 = cryptonote::null_hash;
- bool long_pid = (payment_id.empty())? false : tools::wallet2::parse_long_payment_id(payment_id, pid32);
- if(!payment_id.empty() && !long_pid) {
- m_errorString = "Invalid payment ID";
+ crypto::hash payment_id = cryptonote::null_hash;
+ bool has_long_pid = (payment_id_str.empty())? false : tools::wallet2::parse_long_payment_id(payment_id_str, payment_id);
+
+ // Short payment id provided
+ if(payment_id_str.length() == 16) {
+ m_errorString = tr("Invalid payment ID. Short payment ID should only be used in an integrated address");
+ m_errorCode = Invalid_Payment_Id;
+ return false;
+ }
+
+ // long payment id provided but not valid
+ if(!payment_id_str.empty() && !has_long_pid) {
+ m_errorString = tr("Invalid payment ID");
+ m_errorCode = Invalid_Payment_Id;
+ return false;
+ }
+
+ // integrated + long payment id provided
+ if(has_long_pid && has_short_pid) {
+ m_errorString = tr("Integrated address and long payment id can't be used at the same time");
m_errorCode = Invalid_Payment_Id;
return false;
}
- bool r = m_wallet->m_wallet->add_address_book_row(addr,pid32,description);
+ // Pad short pid with zeros
+ if (has_short_pid)
+ {
+ memcpy(payment_id.data, payment_id_short.data, 8);
+ }
+
+ bool r = m_wallet->m_wallet->add_address_book_row(addr,payment_id,description);
if (r)
refresh();
else
@@ -87,7 +108,16 @@ void AddressBookImpl::refresh()
std::string payment_id = (row->m_payment_id == cryptonote::null_hash)? "" : epee::string_tools::pod_to_hex(row->m_payment_id);
std::string address = cryptonote::get_account_address_as_str(m_wallet->m_wallet->testnet(),row->m_address);
-
+ // convert the zero padded short payment id to integrated address
+ if (payment_id.length() > 16 && payment_id.substr(16).find_first_not_of('0') == std::string::npos) {
+ payment_id = payment_id.substr(0,16);
+ crypto::hash8 payment_id_short;
+ if(tools::wallet2::parse_short_payment_id(payment_id, payment_id_short)) {
+ address = cryptonote::get_account_integrated_address_as_str(m_wallet->m_wallet->testnet(), row->m_address, payment_id_short);
+ // Don't show payment id when integrated address is used
+ payment_id = "";
+ }
+ }
AddressBookRow * abr = new AddressBookRow(i, address, payment_id, row->m_description);
m_rows.push_back(abr);
}
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 da5f776f4..5f7d8e522 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,10 @@ 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;
+ // Connection timeout 30 sec
+ static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30;
}
struct Wallet2CallbackImpl : public tools::i_wallet2_callback
@@ -80,7 +85,7 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
virtual void on_new_block(uint64_t height, const cryptonote::block& block)
{
- LOG_PRINT_L3(__FUNCTION__ << ": new block. height: " << height);
+ //LOG_PRINT_L3(__FUNCTION__ << ": new block. height: " << height);
if (m_listener) {
m_listener->newBlock(height);
@@ -103,6 +108,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 +297,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 +475,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 +643,93 @@ 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();
+ std::unique_ptr<PendingTransactionImpl> transaction(new PendingTransactionImpl(*this));
+
+ 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;
+ return false;
+ }
+
+ if(!transaction->commit()) {
+ m_errorString = transaction->m_errorString;
+ m_status = Status_Error;
+ return false;
+ }
+
+ return true;
+}
+
+bool WalletImpl::exportKeyImages(const string &filename)
+{
+ if (m_wallet->watch_only())
+ {
+ m_errorString = tr("Wallet is view only");
+ m_status = Status_Error;
+ return false;
+ }
+
+ try
+ {
+ if (!m_wallet->export_key_images(filename))
+ {
+ m_errorString = tr("failed to save file ") + filename;
+ m_status = Status_Error;
+ return false;
+ }
+ }
+ catch (std::exception &e)
+ {
+ LOG_ERROR("Error exporting key images: " << e.what());
+ m_errorString = e.what();
+ m_status = Status_Error;
+ return false;
+ }
+ return true;
+}
+
+bool WalletImpl::importKeyImages(const string &filename)
+{
+ try
+ {
+ uint64_t spent = 0, unspent = 0;
+ uint64_t height = m_wallet->import_key_images(filename, spent, unspent);
+ LOG_PRINT_L2("Signed key images imported to height " << height << ", "
+ << print_money(spent) << " spent, " << print_money(unspent) << " unspent");
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR("Error exporting key images: " << e.what());
+ m_errorString = string(tr("Failed to import key images: ")) + e.what();
+ m_status = Status_Error;
+ return false;
+ }
+
+ 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 +747,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)
@@ -935,7 +1088,7 @@ bool WalletImpl::verifySignedMessage(const std::string &message, const std::stri
bool WalletImpl::connectToDaemon()
{
- bool result = m_wallet->check_connection();
+ bool result = m_wallet->check_connection(NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS);
m_status = result ? Status_Ok : Status_Error;
if (!result) {
m_errorString = "Error connecting to daemon at " + m_wallet->get_daemon_address();
@@ -948,7 +1101,7 @@ bool WalletImpl::connectToDaemon()
Wallet::ConnectionStatus WalletImpl::connected() const
{
uint32_t version = 0;
- m_is_connected = m_wallet->check_connection(&version);
+ m_is_connected = m_wallet->check_connection(&version, DEFAULT_CONNECTION_TIMEOUT_MILLIS);
if (!m_is_connected)
return Wallet::ConnectionStatus_Disconnected;
if ((version >> 16) != CORE_RPC_VERSION_MAJOR)
@@ -966,7 +1119,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();
@@ -1020,7 +1178,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();
@@ -1067,8 +1227,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)
@@ -1078,12 +1239,21 @@ void WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction
// in case new wallet, this will force fast-refresh (pulling hashes instead of blocks)
// 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;
}
+
}
@@ -1092,6 +1262,24 @@ bool WalletImpl::parse_uri(const std::string &uri, std::string &address, std::st
return m_wallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error);
}
+bool WalletImpl::rescanSpent()
+{
+ clearStatus();
+ if (!trustedDaemon()) {
+ m_status = Status_Error;
+ m_errorString = tr("Rescan spent can only be used with a trusted daemon");
+ return false;
+ }
+ try {
+ m_wallet->rescan_spent();
+ } catch (const std::exception &e) {
+ LOG_ERROR(__FUNCTION__ << " error: " << e.what());
+ m_status = Status_Error;
+ m_errorString = e.what();
+ return false;
+ }
+ return true;
+}
} // namespace
namespace Bitmonero = Monero;
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index 4520f2565..e3df7fd01 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,13 +92,18 @@ public:
int autoRefreshInterval() const;
void setRefreshFromBlockHeight(uint64_t refresh_from_block_height);
void setRecoveringFromSeed(bool recoveringFromSeed);
-
+ bool watchOnly() const;
+ bool rescanSpent();
PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id,
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);
+ bool exportKeyImages(const std::string &filename);
+ bool importKeyImages(const std::string &filename);
virtual void disposeTransaction(PendingTransaction * t);
virtual TransactionHistory * history() const;
@@ -112,7 +121,7 @@ public:
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;
@@ -120,8 +129,10 @@ private:
bool isNewWallet() const;
void doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit);
+
private:
friend class PendingTransactionImpl;
+ friend class UnsignedTransactionImpl;
friend class TransactionHistoryImpl;
friend struct Wallet2CallbackImpl;
friend class AddressBookImpl;
diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp
index 6b48caf1d..48faa3183 100644
--- a/src/wallet/api/wallet_manager.cpp
+++ b/src/wallet/api/wallet_manager.cpp
@@ -346,7 +346,7 @@ 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;
@@ -384,6 +384,42 @@ uint64_t WalletManagerImpl::blockTarget() const
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::dns_utils::addresses_from_url(address, dnssec_valid);
diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h
index 01752f69b..ca9570254 100644
--- a/src/wallet/api/wallet_manager.h
+++ b/src/wallet/api/wallet_manager.h
@@ -54,6 +54,9 @@ public:
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 4b99e617d..8a03b94af 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -92,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.
@@ -1004,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);
@@ -1808,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());
@@ -1890,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;
@@ -1920,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));
@@ -2223,7 +2234,7 @@ bool wallet2::prepare_file_names(const std::string& file_path)
return true;
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::check_connection(uint32_t *version)
+bool wallet2::check_connection(uint32_t *version, uint32_t timeout)
{
boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex);
@@ -2237,7 +2248,7 @@ bool wallet2::check_connection(uint32_t *version)
u.port = m_testnet ? config::testnet::RPC_DEFAULT_PORT : config::RPC_DEFAULT_PORT;
}
- if (!m_http_client.connect(u.host, std::to_string(u.port), 10000))
+ if (!m_http_client.connect(u.host, std::to_string(u.port), timeout))
return false;
}
@@ -2722,7 +2733,7 @@ float wallet2::get_output_relatedness(const transfer_details &td0, const transfe
return 0.0f;
}
//----------------------------------------------------------------------------------------------------
-size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers) const
+size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers, bool smallest) const
{
std::vector<size_t> candidates;
float best_relatedness = 1.0f;
@@ -2750,13 +2761,30 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve
if (relatedness == best_relatedness)
candidates.push_back(n);
}
- size_t idx = crypto::rand<size_t>() % candidates.size();
+
+ // we have all the least related outputs in candidates, so we can pick either
+ // the smallest, or a random one, depending on request
+ size_t idx;
+ if (smallest)
+ {
+ idx = 0;
+ for (size_t n = 0; n < candidates.size(); ++n)
+ {
+ const transfer_details &td = transfers[unused_indices[candidates[n]]];
+ if (td.amount() < transfers[unused_indices[candidates[idx]]].amount())
+ idx = n;
+ }
+ }
+ else
+ {
+ idx = crypto::rand<size_t>() % candidates.size();
+ }
return pop_index (unused_indices, candidates[idx]);
}
//----------------------------------------------------------------------------------------------------
-size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers) const
+size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers, bool smallest) const
{
- return pop_best_value_from(m_transfers, unused_indices, selected_transfers);
+ return pop_best_value_from(m_transfers, unused_indices, selected_transfers, smallest);
}
//----------------------------------------------------------------------------------------------------
// Select random input sources for transaction.
@@ -2883,6 +2911,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)
@@ -2953,7 +2999,30 @@ 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;
// save as binary
std::ostringstream oss;
@@ -2970,7 +3039,7 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri
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;
@@ -2991,7 +3060,6 @@ 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;
s = s.substr(magiclen);
try
{
@@ -3006,12 +3074,26 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
}
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
@@ -3083,7 +3165,7 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
{
return false;
}
- LOG_PRINT_L2("Saving signed tx data: " << oss.str());
+ 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());
}
//----------------------------------------------------------------------------------------------------
@@ -4044,8 +4126,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
}
}
- // while we have something to send
- while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) {
+ // while:
+ // - we have something to send
+ // - or we need to gather more fee
+ // - or we have just one input in that tx, which is rct (to try and make all/most rct txes 2/2)
+ while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || (use_rct && txes.back().selected_transfers.size() == 1)) {
TX &tx = txes.back();
// if we need to spend money and don't have any left, we fail
@@ -4056,7 +4141,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc)
// This could be more clever, but maybe at the cost of making probabilistic inferences easier
- size_t idx = !prefered_inputs.empty() ? pop_back(prefered_inputs) : !unused_transfers_indices.empty() ? pop_best_value(unused_transfers_indices, tx.selected_transfers) : pop_best_value(unused_dust_indices, tx.selected_transfers);
+ size_t idx;
+ if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee)
+ // the "make rct txes 2/2" case - we pick a small value output to "clean up" the wallet too
+ idx = pop_best_value(unused_dust_indices.empty() ? unused_transfers_indices : unused_dust_indices, tx.selected_transfers, true);
+ else if (!prefered_inputs.empty())
+ idx = pop_back(prefered_inputs);
+ else
+ idx = pop_best_value(unused_transfers_indices.empty() ? unused_dust_indices : unused_transfers_indices, tx.selected_transfers);
const transfer_details &td = m_transfers[idx];
LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount()) << ", ki " << td.m_key_image);
@@ -4163,13 +4255,19 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
else
{
LOG_PRINT_L2("We made a tx, adjusting fee and saving it");
- if (use_rct)
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
- test_tx, test_ptx);
- else
- transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
- detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
- txBlob = t_serializable_object_to_blob(test_ptx.tx);
+ do {
+ if (use_rct)
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
+ test_tx, test_ptx);
+ else
+ transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
+ detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
+ txBlob = t_serializable_object_to_blob(test_ptx.tx);
+ needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier);
+ LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) <<
+ " fee and " << print_money(test_ptx.change_dts.amount) << " change");
+ } while (needed_fee > test_ptx.fee);
+
LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) <<
" fee and " << print_money(test_ptx.change_dts.amount) << " change");
@@ -4778,6 +4876,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
{
@@ -4828,6 +4947,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 e1eafbae3..5a569950f 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -67,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() {}
@@ -98,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"); }
@@ -119,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;
@@ -226,6 +227,8 @@ namespace tools
tx_construction_data construction_data;
};
+ // 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;
@@ -325,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
@@ -386,14 +390,19 @@ 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);
std::vector<wallet2::pending_tx> create_transactions_all(const cryptonote::account_public_address &address, 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_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, 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<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon);
- bool check_connection(uint32_t *version = NULL);
+ bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000);
void get_transfers(wallet2::transfer_container& incoming_transfers) const;
void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0) const;
void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1) const;
@@ -476,6 +485,8 @@ namespace tools
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; }
@@ -516,8 +527,8 @@ namespace tools
std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon);
std::vector<size_t> select_available_mixable_outputs(bool trusted_daemon);
- size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers) const;
- size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers) const;
+ size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers, bool smallest = false) const;
+ size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers, bool smallest = false) const;
void set_tx_note(const crypto::hash &txid, const std::string &note);
std::string get_tx_note(const crypto::hash &txid) const;
@@ -528,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();
@@ -576,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();
@@ -624,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;
diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h
index fcb4ef2e8..5a13205c5 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;
@@ -194,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
@@ -256,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.
@@ -295,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
@@ -324,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
@@ -420,12 +492,42 @@ 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;
+
+ /*!
+ * \brief exportKeyImages - exports key images to file
+ * \param filename
+ * \return - true on success
+ */
+ virtual bool exportKeyImages(const std::string &filename) = 0;
+
+ /*!
+ * \brief importKeyImages - imports key images from file
+ * \param filename
+ * \return - true on success
+ */
+ virtual bool importKeyImages(const std::string &filename) = 0;
+
+
virtual TransactionHistory * history() const = 0;
virtual AddressBook * addressBook() const = 0;
virtual void setListener(WalletListener *) = 0;
@@ -471,6 +573,11 @@ struct Wallet
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;
+ /*
+ * \brief rescanSpent - Rescan spent outputs - Can only be used with trusted daemon
+ * \return true on success
+ */
+ virtual bool rescanSpent() = 0;
};
/**
@@ -571,6 +678,15 @@ struct WalletManager
//! 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 21965c4c8..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);
diff --git a/tests/core_proxy/core_proxy.cpp b/tests/core_proxy/core_proxy.cpp
index 256faeccb..6202eea0d 100644
--- a/tests/core_proxy/core_proxy.cpp
+++ b/tests/core_proxy/core_proxy.cpp
@@ -165,7 +165,7 @@ string tx2str(const cryptonote::transaction& tx, const cryptonote::hash256& tx_h
return ss.str();
}*/
-bool tests::proxy_core::handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed) {
+bool tests::proxy_core::handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) {
if (!keeped_by_block)
return true;
diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h
index 31e876d06..bec9a83bb 100644
--- a/tests/core_proxy/core_proxy.h
+++ b/tests/core_proxy/core_proxy.h
@@ -74,7 +74,7 @@ namespace tests
bool get_stat_info(cryptonote::core_stat_info& st_inf){return true;}
bool have_block(const crypto::hash& id);
bool get_blockchain_top(uint64_t& height, crypto::hash& top_id);
- bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relaued);
+ bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
bool handle_incoming_block(const cryptonote::blobdata& block_blob, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true);
void pause_mine(){}
void resume_mine(){}
diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h
index 047d2c81c..ce204cd9d 100644
--- a/tests/core_tests/chaingen.h
+++ b/tests/core_tests/chaingen.h
@@ -368,7 +368,7 @@ public:
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
size_t pool_size = m_c.get_pool_transactions_count();
- m_c.handle_incoming_tx(t_serializable_object_to_blob(tx), tvc, m_txs_keeped_by_block, false);
+ m_c.handle_incoming_tx(t_serializable_object_to_blob(tx), tvc, m_txs_keeped_by_block, false, false);
bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count();
bool r = check_tx_verification_context(tvc, tx_added, m_ev_index, tx, m_validator);
CHECK_AND_NO_ASSERT_MES(r, false, "tx verification context check failed");
@@ -425,7 +425,7 @@ public:
cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
size_t pool_size = m_c.get_pool_transactions_count();
- m_c.handle_incoming_tx(sr_tx.data, tvc, m_txs_keeped_by_block, false);
+ m_c.handle_incoming_tx(sr_tx.data, tvc, m_txs_keeped_by_block, false, false);
bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count();
cryptonote::transaction tx;
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/ban.cpp b/tests/unit_tests/ban.cpp
index 6b3f029d1..c6a3a14f0 100644
--- a/tests/unit_tests/ban.cpp
+++ b/tests/unit_tests/ban.cpp
@@ -49,7 +49,7 @@ public:
bool get_stat_info(cryptonote::core_stat_info& st_inf) const {return true;}
bool have_block(const crypto::hash& id) const {return true;}
bool get_blockchain_top(uint64_t& height, crypto::hash& top_id)const{height=0;top_id=cryptonote::null_hash;return true;}
- bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relaued) { return true; }
+ bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; }
bool handle_incoming_block(const cryptonote::blobdata& block_blob, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true) { return true; }
void pause_mine(){}
void resume_mine(){}
diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp
index ca72f5e0e..be17f01ad 100644
--- a/tests/unit_tests/hardfork.cpp
+++ b/tests/unit_tests/hardfork.cpp
@@ -51,7 +51,7 @@ public:
virtual std::string get_db_name() const { return std::string(); }
virtual bool lock() { return true; }
virtual void unlock() { }
- virtual void batch_start(uint64_t batch_num_blocks=0) {}
+ virtual bool batch_start(uint64_t batch_num_blocks=0) {}
virtual void batch_stop() {}
virtual void set_batch_transactions(bool) {}
virtual void block_txn_start(bool readonly=false) {}
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/main.cpp b/tests/unit_tests/main.cpp
index faaf9475a..856e81857 100644
--- a/tests/unit_tests/main.cpp
+++ b/tests/unit_tests/main.cpp
@@ -34,6 +34,7 @@
int main(int argc, char** argv)
{
+ epee::string_tools::set_module_name_and_folder(argv[0]);
epee::debug::get_set_enable_assert(true, false);
::testing::InitGoogleTest(&argc, argv);
diff --git a/tests/unit_tests/mnemonics.cpp b/tests/unit_tests/mnemonics.cpp
index 3dc5db7d4..9c497ef29 100644
--- a/tests/unit_tests/mnemonics.cpp
+++ b/tests/unit_tests/mnemonics.cpp
@@ -148,3 +148,22 @@ TEST(mnemonics, all_languages)
test_language(*(*it));
}
}
+
+TEST(mnemonics, language_detection_with_bad_checksum)
+{
+ crypto::secret_key key;
+ std::string language_name;
+ bool res;
+
+ // This Portuguese (4-prefix) seed has all its words with 3-prefix that's also present in English
+ const std::string base_seed = "cinzento luxuriante leonardo gnostico digressao cupula fifa broxar iniquo louvor ovario dorsal ideologo besuntar decurso rosto susto lemure unheiro pagodeiro nitroglicerina eclusa mazurca bigorna";
+ const std::string real_checksum = "gnostico";
+
+ res = crypto::ElectrumWords::words_to_bytes(base_seed, key, language_name);
+ ASSERT_EQ(true, res);
+ ASSERT_STREQ(language_name.c_str(), "Portuguese");
+
+ res = crypto::ElectrumWords::words_to_bytes(base_seed + " " + real_checksum, key, language_name);
+ ASSERT_EQ(true, res);
+ ASSERT_STREQ(language_name.c_str(), "Portuguese");
+}
diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp
index 2be7bde8c..1083dd4cc 100644
--- a/tests/unit_tests/serialization.cpp
+++ b/tests/unit_tests/serialization.cpp
@@ -657,7 +657,7 @@ TEST(Serialization, portability_wallet)
const bool testnet = true;
const bool restricted = false;
tools::wallet2 w(testnet, restricted);
- string wallet_file = "../data/wallet_9svHk1";
+ string wallet_file = epee::string_tools::get_current_module_folder() + "/../../../../tests/data/wallet_9svHk1";
string password = "test";
bool r = false;
try
@@ -777,7 +777,7 @@ TEST(Serialization, portability_wallet)
TEST(Serialization, portability_outputs)
{
// read file
- const std::string filename = "../data/outputs";
+ const std::string filename = epee::string_tools::get_current_module_folder() + "/../../../../tests/data/outputs";
std::string data;
bool r = epee::file_io_utils::load_file_to_string(filename, data);
ASSERT_TRUE(r);
@@ -892,7 +892,7 @@ TEST(Serialization, portability_outputs)
#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003"
TEST(Serialization, portability_unsigned_tx)
{
- const string filename = "../data/unsigned_monero_tx";
+ const string filename = epee::string_tools::get_current_module_folder() + "/../../../../tests/data/unsigned_monero_tx";
std::string s;
const bool testnet = true;
bool r = epee::file_io_utils::load_file_to_string(filename, s);
@@ -1042,7 +1042,7 @@ TEST(Serialization, portability_unsigned_tx)
#define SIGNED_TX_PREFIX "Monero signed tx set\003"
TEST(Serialization, portability_signed_tx)
{
- const string filename = "../data/signed_monero_tx";
+ const string filename = epee::string_tools::get_current_module_folder() + "/../../../../tests/data/signed_monero_tx";
const bool testnet = true;
std::string s;
bool r = epee::file_io_utils::load_file_to_string(filename, s);