diff options
49 files changed, 1398 insertions, 329 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 87a53c368..e42cdb384 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,6 +174,9 @@ include_directories(src contrib/epee/include external "${CMAKE_BINARY_DIR}/versi if(APPLE) include_directories(SYSTEM /usr/include/malloc) + if(POLICY CMP0042) + cmake_policy(SET CMP0042 NEW) + endif() endif() if(MSVC OR MINGW) @@ -375,6 +378,13 @@ else() set(STATIC_ASSERT_FLAG "-Dstatic_assert=_Static_assert") endif() + try_compile(STATIC_ASSERT_CPP_RES "${CMAKE_CURRENT_BINARY_DIR}/static-assert" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/test-static-assert.cpp" COMPILE_DEFINITIONS "-std=c++11") + if(STATIC_ASSERT_CPP_RES) + set(STATIC_ASSERT_CPP_FLAG "") + else() + set(STATIC_ASSERT_CPP_FLAG "-Dstatic_assert=_Static_assert") + endif() + option(COVERAGE "Enable profiling for test coverage report" 0) if(COVERAGE) @@ -410,7 +420,7 @@ else() endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -D_GNU_SOURCE ${MINGW_FLAG} ${STATIC_ASSERT_FLAG} ${WARNINGS} ${C_WARNINGS} ${ARCH_FLAG} ${COVERAGE_FLAGS} ${PIC_FLAG}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -D_GNU_SOURCE ${MINGW_FLAG} ${STATIC_ASSERT_FLAG} ${WARNINGS} ${CXX_WARNINGS} ${ARCH_FLAG} ${COVERAGE_FLAGS} ${PIC_FLAG}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -D_GNU_SOURCE ${MINGW_FLAG} ${STATIC_ASSERT_CPP_FLAG} ${WARNINGS} ${CXX_WARNINGS} ${ARCH_FLAG} ${COVERAGE_FLAGS} ${PIC_FLAG}") # With GCC 6.1.1 the compiled binary malfunctions due to aliasing. Until that # is fixed in the code (Issue #847), force compiler to be conservative. @@ -494,6 +504,13 @@ else() endif(ARM) + if(ANDROID AND NOT BUILD_GUI_DEPS STREQUAL "ON") + #From Android 5: "only position independent executables (PIE) are supported" + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIE") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_CXX_FLAGS} -fPIE -pie") + endif() + if(APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGTEST_HAS_TR1_TUPLE=0") endif() @@ -580,7 +597,7 @@ endif() include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) if(MINGW) set(EXTRA_LIBRARIES mswsock;ws2_32;iphlpapi) -elseif(APPLE OR FREEBSD OR OPENBSD) +elseif(APPLE OR FREEBSD OR OPENBSD OR ANDROID) set(EXTRA_LIBRARIES "") elseif(DRAGONFLY) find_library(COMPAT compat) @@ -70,6 +70,10 @@ release-static-armv7: mkdir -p build/release cd build/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release ../.. && $(MAKE) +release-static-android: + mkdir -p build/release + cd build/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON ../.. && $(MAKE) + release-static-armv8: mkdir -p build/release cd build/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv8-a" -D STATIC=ON -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release ../.. && $(MAKE) diff --git a/cmake/FindLibunwind.cmake b/cmake/FindLibunwind.cmake index 9946e7cdd..af657cad1 100644 --- a/cmake/FindLibunwind.cmake +++ b/cmake/FindLibunwind.cmake @@ -17,6 +17,22 @@ find_path(LIBUNWIND_INCLUDE_DIR libunwind.h ) find_library(LIBUNWIND_LIBRARIES NAMES unwind ) +if(NOT LIBUNWIND_LIBRARIES STREQUAL "LIBUNWIND_LIBRARIES-NOTFOUND") + if (CMAKE_COMPILER_IS_GNUCC) + set(LIBUNWIND_LIBRARIES "gcc_eh;${LIBUNWIND_LIBRARIES}") + endif() +endif() + +# some versions of libunwind need liblzma, and we don't use pkg-config +# so we just look whether liblzma is installed, and add it if it is. +# It might not be actually needed, but doesn't hurt if it is not. +# We don't need any headers, just the lib, as it's privately needed. +message(STATUS "looking for liblzma") +find_library(LIBLZMA_LIBRARIES lzma ) +if(NOT LIBLZMA_LIBRARIES STREQUAL "LIBLZMA_LIBRARIES-NOTFOUND") + message(STATUS "liblzma found") + set(LIBUNWIND_LIBRARIES "${LIBUNWIND_LIBRARIES};${LIBLZMA_LIBRARIES}") +endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Libunwind "Could not find libunwind" LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARIES) diff --git a/cmake/test-static-assert.cpp b/cmake/test-static-assert.cpp new file mode 100644 index 000000000..0c6dfb151 --- /dev/null +++ b/cmake/test-static-assert.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <assert.h> + +static_assert(1, "FAIL"); +int main(int argc, char *argv[]) { + return 0; +} diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h index 2ad92b3f2..554a48488 100644 --- a/contrib/epee/include/console_handler.h +++ b/contrib/epee/include/console_handler.h @@ -133,7 +133,7 @@ namespace epee bool wait_stdin_data() { #if !defined(WIN32) - #ifdef __OpenBSD__ + #if defined(__OpenBSD__) || defined(__ANDROID__) int stdin_fileno = fileno(stdin); #else int stdin_fileno = ::fileno(stdin); diff --git a/contrib/epee/include/net/http_auth.h b/contrib/epee/include/net/http_auth.h index 1931b6115..795d213d9 100644 --- a/contrib/epee/include/net/http_auth.h +++ b/contrib/epee/include/net/http_auth.h @@ -45,15 +45,22 @@ namespace net_utils public: struct login { - login() = delete; + login() : username(), password() {} + login(std::string username_, std::string password_) + : username(std::move(username_)), password(std::move(password_)) + {} + std::string username; std::string password; }; struct session { - session() = delete; - const login credentials; + session(login credentials_) + : credentials(std::move(credentials_)), nonce(), counter(0) + {} + + login credentials; std::string nonce; std::uint32_t counter; }; diff --git a/contrib/otshell_utils/CMakeLists.txt b/contrib/otshell_utils/CMakeLists.txt index 464b125e4..263f07c85 100644 --- a/contrib/otshell_utils/CMakeLists.txt +++ b/contrib/otshell_utils/CMakeLists.txt @@ -3,6 +3,10 @@ project (otshell CXX) # Add executable +if(APPLE AND POLICY CMP0042) + cmake_policy(SET CMP0042 NEW) +endif() + file(GLOB otshell_utils_sources # All files in directory: "*.h" "*.hpp" diff --git a/external/boost/archive/portable_binary_archive.hpp b/external/boost/archive/portable_binary_archive.hpp index 560ef121b..e940c5b9e 100644 --- a/external/boost/archive/portable_binary_archive.hpp +++ b/external/boost/archive/portable_binary_archive.hpp @@ -14,6 +14,7 @@ #include <boost/config.hpp>
#include <boost/cstdint.hpp>
#include <boost/static_assert.hpp>
+#include <boost/archive/archive_exception.hpp>
#include <climits>
#if CHAR_BIT != 8
@@ -37,7 +38,9 @@ enum portable_binary_archive_flags { //#endif
inline void
-reverse_bytes(char size, char *address){
+reverse_bytes(signed char size, char *address){
+ if (size <= 0)
+ throw archive_exception(archive_exception::other_exception);
char * first = address;
char * last = first + size - 1;
for(;first < last;++first, --last){
diff --git a/external/boost/archive/portable_binary_iarchive.hpp b/external/boost/archive/portable_binary_iarchive.hpp index 7149cca0f..7792b530d 100644 --- a/external/boost/archive/portable_binary_iarchive.hpp +++ b/external/boost/archive/portable_binary_iarchive.hpp @@ -234,7 +234,7 @@ namespace boost { namespace archive { inline void
portable_binary_iarchive::load_impl(boost::intmax_t & l, char maxsize){
- char size;
+ signed char size;
l = 0;
this->primitive_base_t::load(size);
@@ -321,6 +321,8 @@ portable_binary_iarchive::init(unsigned int flags){ boost::archive::library_version_type input_library_version;
* this >> input_library_version;
+ // ignore archive version checking
+ /*
// extra little .t is to get around borland quirk
if(boost::archive::BOOST_ARCHIVE_VERSION() < input_library_version)
boost::serialization::throw_exception(
@@ -328,6 +330,7 @@ portable_binary_iarchive::init(unsigned int flags){ boost::archive::archive_exception::unsupported_version
)
);
+ */
#if BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3205))
this->set_library_version(input_library_version);
@@ -354,11 +357,11 @@ namespace detail { template class archive_serializer_map<portable_binary_iarchive>;
}
-template class basic_binary_iprimitive<
- portable_binary_iarchive,
- std::istream::char_type,
- std::istream::traits_type
-> ;
+// template class basic_binary_iprimitive<
+// portable_binary_iarchive,
+// std::istream::char_type,
+// std::istream::traits_type
+//> ;
} // namespace archive
} // namespace boost
diff --git a/external/boost/archive/portable_binary_oarchive.hpp b/external/boost/archive/portable_binary_oarchive.hpp index 8fd7090a1..9ed30d064 100644 --- a/external/boost/archive/portable_binary_oarchive.hpp +++ b/external/boost/archive/portable_binary_oarchive.hpp @@ -225,7 +225,7 @@ portable_binary_oarchive::save_impl( const boost::intmax_t l,
const char maxsize
){
- char size = 0;
+ signed char size = 0;
if(l == 0){
this->primitive_base_t::save(size);
@@ -245,7 +245,7 @@ portable_binary_oarchive::save_impl( }while(ll != 0);
this->primitive_base_t::save(
- static_cast<char>(negative ? -size : size)
+ static_cast<signed char>(negative ? -size : size)
);
if(negative)
@@ -277,10 +277,14 @@ portable_binary_oarchive::init(unsigned int flags) { boost::archive::BOOST_ARCHIVE_SIGNATURE()
);
* this << file_signature;
+ // ignore archive version checking
+ const boost::archive::library_version_type v{};
+ /*
// write library version
const boost::archive::library_version_type v(
boost::archive::BOOST_ARCHIVE_VERSION()
);
+ */
* this << v;
}
save(static_cast<unsigned char>(m_flags >> CHAR_BIT));
@@ -295,11 +299,11 @@ namespace detail { template class archive_serializer_map<portable_binary_oarchive>;
}
-template class basic_binary_oprimitive<
- portable_binary_oarchive,
- std::ostream::char_type,
- std::ostream::traits_type
-> ;
+// template class basic_binary_oprimitive<
+// portable_binary_oarchive,
+// std::ostream::char_type,
+// std::ostream::traits_type
+// > ;
} // namespace archive
} // namespace boost
diff --git a/external/db_drivers/liblmdb/CMakeLists.txt b/external/db_drivers/liblmdb/CMakeLists.txt index 00b203e94..3d55f0eae 100644 --- a/external/db_drivers/liblmdb/CMakeLists.txt +++ b/external/db_drivers/liblmdb/CMakeLists.txt @@ -30,6 +30,11 @@ if(FREEBSD) add_definitions(-DMDB_DSYNC=O_SYNC) endif() +if(ANDROID) + add_definitions("-DANDROID=1") +endif() + + set (lmdb_sources mdb.c midl.c) diff --git a/external/unbound/compat/getentropy_linux.c b/external/unbound/compat/getentropy_linux.c index 37d86a8f1..b8d4a2bd0 100644 --- a/external/unbound/compat/getentropy_linux.c +++ b/external/unbound/compat/getentropy_linux.c @@ -30,7 +30,13 @@ #ifdef HAVE_SYS_SYSCTL_H #include <sys/sysctl.h> #endif +#ifdef __ANDROID__ +#include <sys/vfs.h> +#define statvfs statfs +#define fstatvfs fstatfs +#else #include <sys/statvfs.h> +#endif #include <sys/socket.h> #include <sys/mount.h> #include <sys/mman.h> diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 91c388de6..b39cb1801 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1020,6 +1020,20 @@ public: virtual transaction get_tx(const crypto::hash& h) const = 0; /** + * @brief fetches the transaction with the given hash + * + * The subclass should return the transaction stored which has the given + * hash. + * + * If the transaction does not exist, the subclass should return false. + * + * @param h the hash to look for + * + * @return true iff the transaction was found + */ + virtual bool get_tx(const crypto::hash& h, transaction &tx) const = 0; + + /** * @brief fetches the total number of transactions ever * * The subclass should return a count of all the transactions from diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 1ad9876ac..ba2cb60bd 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1791,7 +1791,7 @@ uint64_t BlockchainLMDB::get_tx_unlock_time(const crypto::hash& h) const return ret; } -transaction BlockchainLMDB::get_tx(const crypto::hash& h) const +bool BlockchainLMDB::get_tx(const crypto::hash& h, transaction &tx) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -1810,19 +1810,27 @@ transaction BlockchainLMDB::get_tx(const crypto::hash& h) const get_result = mdb_cursor_get(m_cur_txs, &val_tx_id, &result, MDB_SET); } if (get_result == MDB_NOTFOUND) - throw1(TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str())); + return false; else if (get_result) throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx from hash", get_result).c_str())); blobdata bd; bd.assign(reinterpret_cast<char*>(result.mv_data), result.mv_size); - transaction tx; if (!parse_and_validate_tx_from_blob(bd, tx)) throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db")); TXN_POSTFIX_RDONLY(); + return true; +} + +transaction BlockchainLMDB::get_tx(const crypto::hash& h) const +{ + transaction tx; + + if (!get_tx(h, tx)) + throw1(TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str())); return tx; } diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 6db5abca1..b0d8d9d0a 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -209,6 +209,8 @@ public: virtual transaction get_tx(const crypto::hash& h) const; + virtual bool get_tx(const crypto::hash& h, transaction &tx) const; + virtual uint64_t get_tx_count() const; virtual std::vector<transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const; diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index e6e53a5c0..35fb9fe6c 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -26,7 +26,10 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "common/command_line.h" +#include "common/i18n.h" #include "common/dns_utils.h" +#include "cryptonote_core/cryptonote_basic_impl.h" #include <cstring> #include <sstream> // check local first (in the event of static or in-source compilation of libunbound) @@ -323,4 +326,144 @@ bool DNSResolver::check_address_syntax(const char *addr) const return true; } +namespace dns_utils +{ + +const char *tr(const char *str) { return i18n_translate(str, "tools::dns_utils"); } + +//----------------------------------------------------------------------- +// TODO: parse the string in a less stupid way, probably with regex +std::string address_from_txt_record(const std::string& s) +{ + // make sure the txt record has "oa1:xmr" and find it + auto pos = s.find("oa1:xmr"); + if (pos == std::string::npos) + return {}; + // search from there to find "recipient_address=" + pos = s.find("recipient_address=", pos); + if (pos == std::string::npos) + return {}; + pos += 18; // move past "recipient_address=" + // find the next semicolon + auto pos2 = s.find(";", pos); + if (pos2 != std::string::npos) + { + // length of address == 95, we can at least validate that much here + if (pos2 - pos == 95) + { + return s.substr(pos, 95); + } + else if (pos2 - pos == 106) // length of address == 106 --> integrated address + { + return s.substr(pos, 106); + } + } + return {}; +} +/** + * @brief gets a monero address from the TXT record of a DNS entry + * + * gets the monero address from the TXT record of the DNS entry associated + * with <url>. If this lookup fails, or the TXT record does not contain an + * XMR address in the correct format, returns an empty string. <dnssec_valid> + * will be set true or false according to whether or not the DNS query passes + * DNSSEC validation. + * + * @param url the url to look up + * @param dnssec_valid return-by-reference for DNSSEC status of query + * + * @return a monero address (as a string) or an empty string + */ +std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid) +{ + std::vector<std::string> addresses; + // get txt records + bool dnssec_available, dnssec_isvalid; + std::string oa_addr = DNSResolver::instance().get_dns_format_from_oa_address(url); + auto records = DNSResolver::instance().get_txt_record(oa_addr, dnssec_available, dnssec_isvalid); + + // TODO: update this to allow for conveying that dnssec was not available + if (dnssec_available && dnssec_isvalid) + { + dnssec_valid = true; + } + else dnssec_valid = false; + + // for each txt record, try to find a monero address in it. + for (auto& rec : records) + { + std::string addr = address_from_txt_record(rec); + if (addr.size()) + { + addresses.push_back(addr); + } + } + return addresses; +} + +std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid) +{ + // attempt to get address from dns query + auto addresses = addresses_from_url(url, dnssec_valid); + if (addresses.empty()) + { + std::cout << tr("wrong address: ") << url; + return {}; + } + // for now, move on only if one address found + if (addresses.size() > 1) + { + std::cout << tr("not yet supported: Multiple Monero addresses found for given URL: ") << url; + return {}; + } + // prompt user for confirmation. + // inform user of DNSSEC validation status as well. + std::string dnssec_str; + if (dnssec_valid) + { + dnssec_str = tr("DNSSEC validation passed"); + } + else + { + dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!"); + } + std::stringstream prompt; + prompt << tr("For URL: ") << url + << ", " << dnssec_str << std::endl + << tr(" Monero Address = ") << addresses[0] + << std::endl + << tr("Is this OK? (Y/n) ") + ; + // prompt the user for confirmation given the dns query and dnssec status + std::string confirm_dns_ok = command_line::input_line(prompt.str()); + if (std::cin.eof()) + { + return {}; + } + if (!command_line::is_yes(confirm_dns_ok)) + { + std::cout << tr("you have cancelled the transfer request") << std::endl; + return {}; + } + return addresses[0]; +} + +bool get_account_address_from_str_or_url( + cryptonote::account_public_address& address + , bool& has_payment_id + , crypto::hash8& payment_id + , bool testnet + , const std::string& str_or_url + ) +{ + if (cryptonote::get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, str_or_url)) + return true; + bool dnssec_valid; + std::string address_str = get_account_address_as_str_from_url(str_or_url, dnssec_valid); + return !address_str.empty() && + cryptonote::get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_str); +} + +} // namespace tools::dns_utils + } // namespace tools diff --git a/src/common/dns_utils.h b/src/common/dns_utils.h index 2e87fb01f..5fe1d4775 100644 --- a/src/common/dns_utils.h +++ b/src/common/dns_utils.h @@ -25,9 +25,11 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#pragma once #include <vector> #include <string> +#include "cryptonote_core/cryptonote_basic.h" namespace tools { @@ -155,4 +157,21 @@ private: DNSResolverData *m_data; }; // class DNSResolver +namespace dns_utils +{ + +std::string address_from_txt_record(const std::string& s); +std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid); + +std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid); +bool get_account_address_from_str_or_url( + cryptonote::account_public_address& address + , bool& has_payment_id + , crypto::hash8& payment_id + , bool testnet + , const std::string& str_or_url + ); + +} // namespace tools::dns_utils + } // namespace tools diff --git a/src/common/int-util.h b/src/common/int-util.h index e9eddee9c..9ac20e582 100644 --- a/src/common/int-util.h +++ b/src/common/int-util.h @@ -36,6 +36,10 @@ #include <string.h> #include <sys/param.h> +#if defined(__ANDROID__) +#include <byteswap.h> +#endif + #if defined(_MSC_VER) #include <stdlib.h> @@ -138,16 +142,24 @@ static inline uint32_t ident32(uint32_t x) { return x; } static inline uint64_t ident64(uint64_t x) { return x; } #ifndef __OpenBSD__ +# if defined(__ANDROID__) && defined(__swap32) && !defined(swap32) +# define swap32 __swap32 +# elif !defined(swap32) static inline uint32_t swap32(uint32_t x) { x = ((x & 0x00ff00ff) << 8) | ((x & 0xff00ff00) >> 8); return (x << 16) | (x >> 16); } +# endif +# if defined(__ANDROID__) && defined(__swap64) && !defined(swap64) +# define swap64 __swap64 +# elif !defined(swap64) static inline uint64_t swap64(uint64_t x) { x = ((x & 0x00ff00ff00ff00ff) << 8) | ((x & 0xff00ff00ff00ff00) >> 8); x = ((x & 0x0000ffff0000ffff) << 16) | ((x & 0xffff0000ffff0000) >> 16); return (x << 32) | (x >> 32); } -#endif +# endif +#endif /* __OpenBSD__ */ #if defined(__GNUC__) #define UNUSED __attribute__((unused)) diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index 9d83caca8..1e037a07d 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -89,3 +89,14 @@ if (ARM) PROPERTY COMPILE_DEFINITIONS "NO_OPTIMIZED_MULTIPLY_ON_ARM") endif() endif() + +# Because of the way Qt works on android with JNI, the code does not live in the main android thread +# So this code runs with a 1 MB default stack size. +# This will force the use of the heap for the allocation of the scratchpad +if (ANDROID) + if( BUILD_GUI_DEPS ) + add_definitions(-DFORCE_USE_HEAP=1) + endif() +endif() + + diff --git a/src/crypto/oaes_lib.c b/src/crypto/oaes_lib.c index f054a16f4..0afec6212 100644 --- a/src/crypto/oaes_lib.c +++ b/src/crypto/oaes_lib.c @@ -39,8 +39,8 @@ #include <malloc.h> #endif -// FreeBSD, and OpenBSD also don't need timeb.h -#if !defined(__FreeBSD__) && !defined(__OpenBSD__) +// ANDROID, FreeBSD, and OpenBSD also don't need timeb.h +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__ANDROID__) #include <sys/timeb.h> #else #include <sys/time.h> @@ -499,7 +499,7 @@ static void oaes_get_seed( char buf[RANDSIZ + 1] ) #else static uint32_t oaes_get_seed(void) { - #if !defined(__FreeBSD__) && !defined(__OpenBSD__) + #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__ANDROID__) struct timeb timer; struct tm *gmTimer; char * _test = NULL; diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c index 66d9ca5d9..43b9619f3 100644 --- a/src/crypto/slow-hash.c +++ b/src/crypto/slow-hash.c @@ -1052,7 +1052,6 @@ STATIC INLINE void xor_blocks(uint8_t* a, const uint8_t* b) void cn_slow_hash(const void *data, size_t length, char *hash) { - uint8_t long_state[MEMORY]; uint8_t text[INIT_SIZE_BYTE]; uint8_t a[AES_BLOCK_SIZE]; uint8_t b[AES_BLOCK_SIZE]; @@ -1070,6 +1069,13 @@ void cn_slow_hash(const void *data, size_t length, char *hash) hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein }; +#ifndef FORCE_USE_HEAP + uint8_t long_state[MEMORY]; +#else + uint8_t *long_state = NULL; + long_state = (uint8_t *)malloc(MEMORY); +#endif + hash_process(&state.hs, data, length); memcpy(text, state.init, INIT_SIZE_BYTE); @@ -1129,6 +1135,9 @@ void cn_slow_hash(const void *data, size_t length, char *hash) memcpy(state.init, text, INIT_SIZE_BYTE); hash_permutation(&state.hs); extra_hashes[state.hs.b[0] & 3](&state, 200, hash); +#ifdef FORCE_USE_HEAP + free(long_state); +#endif } #endif /* !aarch64 || !crypto */ diff --git a/src/crypto/tree-hash.c b/src/crypto/tree-hash.c index d73f0d959..5cdaa8c94 100644 --- a/src/crypto/tree-hash.c +++ b/src/crypto/tree-hash.c @@ -40,27 +40,28 @@ #include <stdlib.h> #endif -/// Quick check if this is power of two (use on unsigned types; in this case for size_t only) -bool ispowerof2_size_t(size_t x) { - return x && !(x & (x - 1)); -} - /*** * Round to power of two, for count>=3 and for count being not too large (as reasonable for tree hash calculations) */ size_t tree_hash_cnt(size_t count) { - assert( count >= 3); // cases for 0,1,2 are handled elsewhere - // Round down the count size: fun(2**n)= 2**(n-1) to round down to power of two - size_t tmp = count - 1; - size_t jj = 1; - for (jj=1 ; tmp != 0 ; ++jj) { - tmp /= 2; // dividing by 2 until to get how many powers of 2 fits size_to tmp - } - size_t cnt = 1 << (jj-2); // cnt is the count, but rounded down to power of two - // printf("count=%zu cnt=%zu jj=%zu tmp=%zu \n" , count,cnt,jj,tmp); - assert( cnt > 0 ); assert( cnt >= count/2 ); assert( cnt <= count ); - assert( ispowerof2_size_t( cnt )); - return cnt; + // This algo has some bad history but all we are doing is 1 << floor(log2(count)) + // There are _many_ ways to do log2, for some reason the one selected was the most obscure one, + // and fixing it made it even more obscure. + // + // Iterative method implemented below aims for clarity over speed, if performance is needed + // then my advice is to use the BSR instruction on x86 + // + // All the paranoid asserts have been removed since it is trivial to mathematically prove that + // the return will always be a power of 2. + // Problem space has been defined as 3 <= count <= 2^28. Of course quarter of a billion transactions + // is not a sane upper limit for a block, so there will be tighter limits in other parts of the code + + assert( count >= 3 ); // cases for 0,1,2 are handled elsewhere + assert( count <= 0x10000000 ); // sanity limit to 2^28, MSB=1 will cause an inf loop + + size_t pow = 2; + while(pow < count) pow <<= 1; + return pow >> 1; } void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) { @@ -86,9 +87,6 @@ void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) { size_t i, j; size_t cnt = tree_hash_cnt( count ); - size_t max_size_t = (size_t) -1; // max allowed value of size_t - assert( cnt < max_size_t/2 ); // reasonable size to avoid any overflows. /2 is extra; Anyway should be limited much stronger by logical code - // as we have sane limits on transactions counts in blockchain rules char (*ints)[HASH_SIZE]; size_t ints_size = cnt * HASH_SIZE; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 42279184a..5f9d6937b 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -525,6 +525,7 @@ bool Blockchain::reset_and_set_genesis_block(const block& b) { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); + m_timestamps_and_difficulties_height = 0; m_alternative_chains.clear(); m_db->reset(); m_hardfork->init(); @@ -1084,7 +1085,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m size_t txs_size; uint64_t fee; - if (!m_tx_pool.fill_block_template(b, median_size, already_generated_coins, txs_size, fee)) + if (!m_tx_pool.fill_block_template(b, median_size, already_generated_coins, txs_size, fee, m_hardfork->get_current_version())) { return false; } @@ -1895,11 +1896,11 @@ bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container { try { - txs.push_back(m_db->get_tx(tx_hash)); - } - catch (const TX_DNE& e) - { - missed_txs.push_back(tx_hash); + transaction tx; + if (m_db->get_tx(tx_hash, tx)) + txs.push_back(std::move(tx)); + else + missed_txs.push_back(tx_hash); } catch (const std::exception& e) { diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h index 704b8467d..a6610e60d 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -36,7 +36,8 @@ #include "crypto/crypto.h" #include "crypto/hash.h" #include "ringct/rctOps.h" - +#include <boost/serialization/vector.hpp> +#include <boost/serialization/utility.hpp> namespace cryptonote { @@ -62,16 +63,6 @@ namespace cryptonote rct::key mask; //ringct amount mask void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } - - BEGIN_SERIALIZE_OBJECT() - FIELD(outputs) - VARINT_FIELD(real_output) - FIELD(real_out_tx_key) - VARINT_FIELD(real_output_in_tx_index) - VARINT_FIELD(amount) - FIELD(rct) - FIELD(mask) - END_SERIALIZE() }; struct tx_destination_entry @@ -261,3 +252,23 @@ namespace cryptonote specific_type& variable_name = boost::get<specific_type>(variant_var); } + +BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0) + +namespace boost +{ + namespace serialization + { + template <class Archive> + inline void serialize(Archive &a, cryptonote::tx_source_entry &x, const boost::serialization::version_type ver) + { + a & x.outputs; + a & x.real_output; + a & x.real_out_tx_key; + a & x.real_output_in_tx_index; + a & x.amount; + a & x.rct; + a & x.mask; + } + } +} diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 4cfd61f9f..59ac534fe 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -60,6 +60,7 @@ namespace cryptonote size_t const TRANSACTION_SIZE_LIMIT_V2 = (((CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); time_t const MIN_RELAY_TIME = (60 * 5); // only start re-relaying transactions after that many seconds time_t const MAX_RELAY_TIME = (60 * 60 * 4); // at most that many seconds between resends + float const ACCEPT_THRESHOLD = 0.99f; // a kind of increasing backoff within min/max bounds time_t get_relay_delay(time_t now, time_t received) @@ -69,6 +70,11 @@ namespace cryptonote d = MAX_RELAY_TIME; return d; } + + uint64_t template_accept_threshold(uint64_t amount) + { + return amount * ACCEPT_THRESHOLD; + } } //--------------------------------------------------------------------------------- //--------------------------------------------------------------------------------- @@ -189,6 +195,8 @@ namespace cryptonote txd_p.first->second.fee = fee; txd_p.first->second.max_used_block_id = null_hash; txd_p.first->second.max_used_block_height = 0; + txd_p.first->second.last_failed_height = 0; + txd_p.first->second.last_failed_id = null_hash; txd_p.first->second.kept_by_block = kept_by_block; txd_p.first->second.receive_time = time(nullptr); txd_p.first->second.last_relayed_time = time(NULL); @@ -585,7 +593,7 @@ namespace cryptonote } //--------------------------------------------------------------------------------- //TODO: investigate whether boolean return is appropriate - bool tx_memory_pool::fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee) + bool tx_memory_pool::fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee, uint8_t version) { // Warning: This function takes already_generated_ // coins as an argument and appears to do nothing @@ -593,47 +601,51 @@ namespace cryptonote CRITICAL_REGION_LOCAL(m_transactions_lock); + uint64_t best_coinbase = 0; total_size = 0; fee = 0; - // Maximum block size is 130% of the median block size. This gives a - // little extra headroom for the max size transaction. - size_t max_total_size = (130 * median_size) / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + size_t max_total_size = 2 * median_size - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; std::unordered_set<crypto::key_image> k_images; + LOG_PRINT_L2("Filling block template, median size " << median_size << ", " << m_txs_by_fee.size() << " txes in the pool"); auto sorted_it = m_txs_by_fee.begin(); while (sorted_it != m_txs_by_fee.end()) { auto tx_it = m_transactions.find(sorted_it->second); + LOG_PRINT_L2("Considering " << tx_it->first << ", size " << tx_it->second.blob_size << ", current block size " << total_size << "/" << max_total_size << ", current coinbase " << print_money(best_coinbase)); // Can not exceed maximum block size if (max_total_size < total_size + tx_it->second.blob_size) { + LOG_PRINT_L2(" would exceed maximum block size"); sorted_it++; continue; } - // If adding this tx will make the block size - // greater than CRYPTONOTE_GETBLOCKTEMPLATE_MAX - // _BLOCK_SIZE bytes, reject the tx; this will - // keep block sizes from becoming too unwieldly - // to propagate at 60s block times. - if ( (total_size + tx_it->second.blob_size) > CRYPTONOTE_GETBLOCKTEMPLATE_MAX_BLOCK_SIZE ) + // If we're getting lower coinbase tx, + // stop including more tx + uint64_t block_reward; + if(!get_block_reward(median_size, total_size + tx_it->second.blob_size, already_generated_coins, block_reward, version)) { + LOG_PRINT_L2(" would exceed maximum block size"); + sorted_it++; + continue; + } + uint64_t coinbase = block_reward + fee; + if (coinbase < template_accept_threshold(best_coinbase)) + { + LOG_PRINT_L2(" would decrease coinbase to " << print_money(coinbase)); sorted_it++; continue; } - - // If we've exceeded the penalty free size, - // stop including more tx - if (total_size > median_size) - break; // Skip transactions that are not ready to be // included into the blockchain or that are // missing key images if (!is_transaction_ready_to_go(tx_it->second) || have_key_images(k_images, tx_it->second.tx)) { + LOG_PRINT_L2(" not ready to go, or key images already seen"); sorted_it++; continue; } @@ -641,10 +653,15 @@ namespace cryptonote bl.tx_hashes.push_back(tx_it->first); total_size += tx_it->second.blob_size; fee += tx_it->second.fee; + best_coinbase = coinbase; append_key_images(k_images, tx_it->second.tx); sorted_it++; + LOG_PRINT_L2(" added, new block size " << total_size << "/" << max_total_size << ", coinbase " << print_money(best_coinbase)); } + LOG_PRINT_L2("Block template filled with " << bl.tx_hashes.size() << " txes, size " + << total_size << "/" << max_total_size << ", coinbase " << print_money(best_coinbase) + << " (including " << print_money(fee) << " in fees)"); return true; } //--------------------------------------------------------------------------------- diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 794b86719..32a05b0f4 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -216,10 +216,11 @@ namespace cryptonote * @param already_generated_coins the current total number of coins "minted" * @param total_size return-by-reference the total size of the new block * @param fee return-by-reference the total of fees from the included transactions + * @param version hard fork version to use for consensus rules * * @return true */ - bool fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee); + bool fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee, uint8_t version); /** * @brief get a list of all transactions in the pool diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index f07ef1616..7381dd06f 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -26,7 +26,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "cryptonote_core/cryptonote_basic_impl.h" +#include "common/dns_utils.h" #include "daemon/command_parser_executor.h" namespace daemonize { @@ -238,17 +238,35 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg } cryptonote::account_public_address adr; + bool has_payment_id; + crypto::hash8 payment_id; bool testnet = false; - if(!cryptonote::get_account_address_from_str(adr, false, args.front())) + if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, args.front())) { - if(!cryptonote::get_account_address_from_str(adr, true, args.front())) + if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, args.front())) { - std::cout << "target account address has wrong format" << std::endl; - return true; + bool dnssec_valid; + std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(args.front(), dnssec_valid); + if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, address_str)) + { + if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, address_str)) + { + std::cout << "target account address has wrong format" << std::endl; + return true; + } + else + { + testnet = true; + } + } + } + else + { + testnet = true; } - testnet = true; - std::cout << "Mining to a testnet address, make sure this is intentional!" << std::endl; } + if(testnet) + std::cout << "Mining to a testnet address, make sure this is intentional!" << std::endl; uint64_t threads_count = 1; if(args.size() > 2) { diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 41b1fad24..4d415b4c2 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -473,8 +473,9 @@ bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, u if (!first) std::cout << std::endl; std::cout + << "height: " << header.height << ", timestamp: " << header.timestamp << ", difficulty: " << header.difficulty + << ", size: " << header.block_size << std::endl << "major version: " << (unsigned)header.major_version << ", minor version: " << (unsigned)header.minor_version << std::endl - << "height: " << header.height << ", timestamp: " << header.timestamp << ", difficulty: " << header.difficulty << std::endl << "block id: " << header.hash << ", previous block id: " << header.prev_hash << std::endl << "difficulty: " << header.difficulty << ", nonce " << header.nonce << ", reward " << cryptonote::print_money(header.reward) << std::endl; first = false; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index c2ff63fc7..6ca01ed2c 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -143,6 +143,7 @@ namespace cryptonote res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count(); res.testnet = m_testnet; res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1); + res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit(); res.status = CORE_RPC_STATUS_OK; return true; } @@ -891,6 +892,7 @@ namespace cryptonote response.hash = string_tools::pod_to_hex(hash); response.difficulty = m_core.get_blockchain_storage().block_difficulty(height); response.reward = get_block_reward(blk); + response.block_size = m_core.get_blockchain_storage().get_db().get_block_size(height); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1161,6 +1163,7 @@ namespace cryptonote res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count(); res.testnet = m_testnet; res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1); + res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit(); res.status = CORE_RPC_STATUS_OK; return true; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index b09cad235..c08e43066 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,7 +49,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 1 -#define CORE_RPC_VERSION_MINOR 0 +#define CORE_RPC_VERSION_MINOR 3 #define CORE_RPC_VERSION (((CORE_RPC_VERSION_MAJOR)<<16)|(CORE_RPC_VERSION_MINOR)) struct COMMAND_RPC_GET_HEIGHT @@ -512,6 +512,7 @@ namespace cryptonote bool testnet; std::string top_block_hash; uint64_t cumulative_difficulty; + uint64_t block_size_limit; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -529,6 +530,7 @@ namespace cryptonote KV_SERIALIZE(testnet) KV_SERIALIZE(top_block_hash) KV_SERIALIZE(cumulative_difficulty) + KV_SERIALIZE(block_size_limit) END_KV_SERIALIZE_MAP() }; }; @@ -693,6 +695,7 @@ namespace cryptonote std::string hash; difficulty_type difficulty; uint64_t reward; + uint64_t block_size; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(major_version) @@ -706,6 +709,7 @@ namespace cryptonote KV_SERIALIZE(hash) KV_SERIALIZE(difficulty) KV_SERIALIZE(reward) + KV_SERIALIZE(block_size) END_KV_SERIALIZE_MAP() }; @@ -919,7 +923,7 @@ namespace cryptonote KV_SERIALIZE(last_failed_id_hash) KV_SERIALIZE(receive_time) KV_SERIALIZE(relayed) - KV_SERIALIZE(last_failed_id_hash) + KV_SERIALIZE(last_relayed_time) END_KV_SERIALIZE_MAP() }; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index ba950f803..088d5e2ac 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -46,6 +46,7 @@ #include "common/i18n.h" #include "common/command_line.h" #include "common/util.h" +#include "common/dns_utils.h" #include "p2p/net_node.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "simplewallet.h" @@ -563,6 +564,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>")); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address")); m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); + m_cmd_binder.set_handler("address_book", boost::bind(&simple_wallet::address_book, this, _1), tr("address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)] - Print all entries in the address book, optionally adding/deleting an entry to/from it")); m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), tr("Save wallet data")); m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), tr("Save a watch-only keys file")); m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), tr("Display private view key")); @@ -573,6 +575,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>")); m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range")); + m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [<min_amount> <max_amount>] - Show unspent outputs within an optional amount range)")); m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch")); m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("Set an arbitrary string note for a txid")); m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), tr("Get a string note for a txid")); @@ -612,9 +615,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) } else if (args[1] == "language") { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - seed_set_language(local_args); + seed_set_language(args); return true; } } @@ -627,9 +628,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) } else { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_always_confirm_transfers(local_args); + set_always_confirm_transfers(args); return true; } } @@ -642,9 +641,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) } else { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_store_tx_info(local_args); + set_store_tx_info(args); return true; } } @@ -657,9 +654,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) } else { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_default_mixin(local_args); + set_default_mixin(args); return true; } } @@ -672,9 +667,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) } else { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_auto_refresh(local_args); + set_auto_refresh(args); return true; } } @@ -688,9 +681,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) } else { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_refresh_type(local_args); + set_refresh_type(args); return true; } } @@ -703,9 +694,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) } else { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_default_priority(local_args); + set_default_priority(args); return true; } } @@ -718,9 +707,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) } else { - std::vector<std::string> local_args = args; - local_args.erase(local_args.begin(), local_args.begin()+2); - set_confirm_missing_payment_id(local_args); + set_confirm_missing_payment_id(args); return true; } } @@ -1878,75 +1865,6 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::get_address_from_str(const std::string &str, cryptonote::account_public_address &address, bool &has_payment_id, crypto::hash8 &payment_id) -{ - if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), str)) - { - // if treating as an address fails, try as url - bool dnssec_ok = false; - std::string url = str; - - // attempt to get address from dns query - auto addresses_from_dns = tools::wallet2::addresses_from_url(url, dnssec_ok); - - // for now, move on only if one address found - if (addresses_from_dns.size() == 1) - { - if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), addresses_from_dns[0])) - { - // if it was an address, prompt user for confirmation. - // inform user of DNSSEC validation status as well. - - std::string dnssec_str; - if (dnssec_ok) - { - dnssec_str = tr("DNSSEC validation passed"); - } - else - { - dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!"); - } - std::stringstream prompt; - prompt << tr("For URL: ") << url - << ", " << dnssec_str << std::endl - << tr(" Monero Address = ") << addresses_from_dns[0] - << std::endl - << tr("Is this OK? (Y/n) ") - ; - - // prompt the user for confirmation given the dns query and dnssec status - std::string confirm_dns_ok = command_line::input_line(prompt.str()); - if (std::cin.eof()) - { - return false; - } - if (!command_line::is_yes(confirm_dns_ok)) - { - fail_msg_writer() << tr("you have cancelled the transfer request"); - return false; - } - } - else - { - fail_msg_writer() << tr("failed to get a Monero address from: ") << url; - return false; - } - } - else if (addresses_from_dns.size() > 1) - { - fail_msg_writer() << tr("not yet supported: Multiple Monero addresses found for given URL: ") << url; - return false; - } - else - { - fail_msg_writer() << tr("wrong address: ") << url; - return false; - } - } - - return true; -} -//---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) { if (!try_connect_to_daemon()) @@ -2039,7 +1957,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri cryptonote::tx_destination_entry de; bool has_payment_id; crypto::hash8 new_payment_id; - if (!get_address_from_str(local_args[i], de.addr, has_payment_id, new_payment_id)) + if (!tools::dns_utils::get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[i])) return true; if (has_payment_id) @@ -2527,7 +2445,7 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_) bool has_payment_id; crypto::hash8 new_payment_id; cryptonote::account_public_address address; - if (!get_address_from_str(local_args[0], address, has_payment_id, new_payment_id)) + if (!tools::dns_utils::get_account_address_from_str_or_url(address, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[0])) return true; if (has_payment_id) @@ -3075,7 +2993,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), local_args[2])) + if(!tools::dns_utils::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), local_args[2])) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -3383,6 +3301,124 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) +{ + if(!args_.empty() && args_.size() != 2) { + fail_msg_writer() << tr("usage: unspent_outputs [<min_amount> <max_amount>]"); + return true; + } + uint64_t min_amount = 0; + uint64_t max_amount = std::numeric_limits<uint64_t>::max(); + if (args_.size() == 2) + { + if (!cryptonote::parse_amount(min_amount, args_[0]) || !cryptonote::parse_amount(max_amount, args_[1])) + { + fail_msg_writer() << tr("amount is wrong: ") << args_[0] << ' ' << args_[1] << + ", " << tr("expected number from 0 to ") << print_money(std::numeric_limits<uint64_t>::max()); + return true; + } + if (min_amount > max_amount) + { + fail_msg_writer() << tr("<min_amount> should be smaller than <max_amount>"); + return true; + } + } + tools::wallet2::transfer_container transfers; + m_wallet->get_transfers(transfers); + if (transfers.empty()) + { + success_msg_writer() << "There is no unspent output in this wallet."; + return true; + } + std::map<uint64_t, tools::wallet2::transfer_container> amount_to_tds; + uint64_t min_height = std::numeric_limits<uint64_t>::max(); + uint64_t max_height = 0; + uint64_t found_min_amount = std::numeric_limits<uint64_t>::max(); + uint64_t found_max_amount = 0; + uint64_t count = 0; + for (const auto& td : transfers) + { + uint64_t amount = td.amount(); + if (td.m_spent || amount < min_amount || amount > max_amount) + continue; + amount_to_tds[amount].push_back(td); + if (min_height > td.m_block_height) min_height = td.m_block_height; + if (max_height < td.m_block_height) max_height = td.m_block_height; + if (found_min_amount > amount) found_min_amount = amount; + if (found_max_amount < amount) found_max_amount = amount; + ++count; + } + for (const auto& amount_tds : amount_to_tds) + { + auto& tds = amount_tds.second; + success_msg_writer() << tr("\nAmount: ") << print_money(amount_tds.first) << tr(", number of keys: ") << tds.size(); + for (size_t i = 0; i < tds.size(); ) + { + std::ostringstream oss; + for (size_t j = 0; j < 8 && i < tds.size(); ++i, ++j) + oss << tds[i].m_block_height << tr(" "); + success_msg_writer() << oss.str(); + } + } + success_msg_writer() + << tr("\nMin block height: ") << min_height + << tr("\nMax block height: ") << max_height + << tr("\nMin amount found: ") << print_money(found_min_amount) + << tr("\nMax amount found: ") << print_money(found_max_amount) + << tr("\nTotal count: ") << count; + const size_t histogram_height = 10; + const size_t histogram_width = 50; + double bin_size = (max_height - min_height + 1.0) / histogram_width; + size_t max_bin_count = 0; + std::vector<size_t> histogram(histogram_width, 0); + for (const auto& amount_tds : amount_to_tds) + { + for (auto& td : amount_tds.second) + { + uint64_t bin_index = (td.m_block_height - min_height + 1) / bin_size; + if (bin_index >= histogram_width) + bin_index = histogram_width - 1; + histogram[bin_index]++; + if (max_bin_count < histogram[bin_index]) + max_bin_count = histogram[bin_index]; + } + } + for (size_t x = 0; x < histogram_width; ++x) + { + double bin_count = histogram[x]; + if (max_bin_count > histogram_height) + bin_count *= histogram_height / (double)max_bin_count; + if (histogram[x] > 0 && bin_count < 1.0) + bin_count = 1.0; + histogram[x] = bin_count; + } + std::vector<std::string> histogram_line(histogram_height, std::string(histogram_width, ' ')); + for (size_t y = 0; y < histogram_height; ++y) + { + for (size_t x = 0; x < histogram_width; ++x) + { + if (y < histogram[x]) + histogram_line[y][x] = '*'; + } + } + double count_per_star = max_bin_count / (double)histogram_height; + if (count_per_star < 1) + count_per_star = 1; + success_msg_writer() + << tr("\nBin size: ") << bin_size + << tr("\nOutputs per *: ") << count_per_star; + ostringstream histogram_str; + histogram_str << tr("count\n ^\n"); + for (size_t y = histogram_height; y > 0; --y) + histogram_str << tr(" |") << histogram_line[y - 1] << tr("|\n"); + histogram_str + << tr(" +") << std::string(histogram_width, '-') << tr("+--> block height\n") + << tr(" ^") << std::string(histogram_width - 2, ' ') << tr("^\n") + << tr(" ") << min_height << std::string(histogram_width - 8, ' ') << max_height; + success_msg_writer() << histogram_str.str(); + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_) { return refresh_main(0, true); @@ -3490,6 +3526,86 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + if (args.size() == 0) + { + } + else if (args.size() == 1 || (args[0] != "add" && args[0] != "delete")) + { + fail_msg_writer() << tr("usage: address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)]"); + return true; + } + else if (args[0] == "add") + { + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 payment_id8; + if (!get_address_from_str(args[1], address, has_payment_id, payment_id8)) + { + fail_msg_writer() << tr("failed to parse address"); + return true; + } + crypto::hash payment_id = null_hash; + size_t description_start = 2; + if (has_payment_id) + { + memcpy(payment_id.data, payment_id8.data, 8); + } + else if (!has_payment_id && args.size() >= 4 && args[2] == "pid") + { + if (tools::wallet2::parse_long_payment_id(args[3], payment_id)) + { + description_start += 2; + } + else if (tools::wallet2::parse_short_payment_id(args[3], payment_id8)) + { + memcpy(payment_id.data, payment_id8.data, 8); + description_start += 2; + } + else + { + fail_msg_writer() << tr("failed to parse payment ID"); + return true; + } + } + std::string description; + for (size_t i = description_start; i < args.size(); ++i) + { + if (i > description_start) + description += " "; + description += args[i]; + } + m_wallet->add_address_book_row(address, payment_id, description); + } + else + { + size_t row_id; + if(!epee::string_tools::get_xtype_from_string(row_id, args[1])) + { + fail_msg_writer() << tr("failed to parse index"); + return true; + } + m_wallet->delete_address_book_row(row_id); + } + auto address_book = m_wallet->get_address_book(); + if (address_book.empty()) + { + success_msg_writer() << tr("Address book is empty."); + } + else + { + for (size_t i = 0; i < address_book.size(); ++i) { + auto& row = address_book[i]; + success_msg_writer() << tr("Index: ") << i; + success_msg_writer() << tr("Address: ") << get_account_address_as_str(m_wallet->testnet(), row.m_address); + success_msg_writer() << tr("Payment ID: ") << row.m_payment_id; + success_msg_writer() << tr("Description: ") << row.m_description << "\n"; + } + } + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::set_tx_note(const std::vector<std::string> &args) { if (args.size() == 0) @@ -3615,7 +3731,7 @@ bool simple_wallet::verify(const std::vector<std::string> &args) cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), address_string)) + if(!tools::dns_utils::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), address_string)) { fail_msg_writer() << tr("failed to parse address"); return true; diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 05e8356e9..753bca74d 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -135,6 +135,7 @@ namespace cryptonote ); bool print_address(const std::vector<std::string> &args = std::vector<std::string>()); bool print_integrated_address(const std::vector<std::string> &args = std::vector<std::string>()); + bool address_book(const std::vector<std::string> &args = std::vector<std::string>()); bool save(const std::vector<std::string> &args); bool save_watch_only(const std::vector<std::string> &args); bool set_variable(const std::vector<std::string> &args); @@ -143,6 +144,7 @@ namespace cryptonote bool get_tx_key(const std::vector<std::string> &args); bool check_tx_key(const std::vector<std::string> &args); bool show_transfers(const std::vector<std::string> &args); + bool unspent_outputs(const std::vector<std::string> &args); bool rescan_blockchain(const std::vector<std::string> &args); bool refresh_main(uint64_t start_height, bool reset = false); bool set_tx_note(const std::vector<std::string> &args); @@ -163,7 +165,6 @@ namespace cryptonote bool accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message = std::string()); bool accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs); bool accept_loaded_tx(const tools::wallet2::signed_tx_set &txs); - bool get_address_from_str(const std::string &str, cryptonote::account_public_address &address, bool &has_payment_id, crypto::hash8 &payment_id); /*! * \brief Prints the seed with a nice message diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp index bbf96c81a..b878491ce 100644 --- a/src/wallet/api/address_book.cpp +++ b/src/wallet/api/address_book.cpp @@ -103,6 +103,28 @@ bool AddressBookImpl::deleteRow(std::size_t rowId) return r; } +int AddressBookImpl::lookupPaymentID(const std::string &payment_id) const +{ + // turn short ones into long ones for comparison + const std::string long_payment_id = payment_id + std::string(64 - payment_id.size(), '0'); + + int idx = -1; + for (const auto &row: m_rows) { + ++idx; + // this does short/short and long/long + if (payment_id == row->getPaymentId()) + return idx; + // short/long + if (long_payment_id == row->getPaymentId()) + return idx; + // one case left: payment_id was long, row's is short + const std::string long_row_payment_id = row->getPaymentId() + std::string(64 - row->getPaymentId().size(), '0'); + if (payment_id == long_row_payment_id) + return idx; + } + return -1; +} + void AddressBookImpl::clearRows() { for (auto r : m_rows) { delete r; diff --git a/src/wallet/api/address_book.h b/src/wallet/api/address_book.h index 7f30e4387..33d06a078 100644 --- a/src/wallet/api/address_book.h +++ b/src/wallet/api/address_book.h @@ -51,6 +51,8 @@ public: // Error codes. See AddressBook:ErrorCode enum in wallet2_api.h std::string errorString() const {return m_errorString;} int errorCode() const {return m_errorCode;} + + int lookupPaymentID(const std::string &payment_id) const; private: void clearRows(); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 3a4493ec3..5caa52257 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1010,9 +1010,6 @@ void WalletImpl::doRefresh() // Syncing daemon and refreshing wallet simultaneously is very resource intensive. // Disable refresh if wallet is disconnected or daemon isn't synced. if (daemonSynced()) { - // Use fast refresh for new wallets - if (isNewWallet()) - m_wallet->set_refresh_from_block_height(daemonBlockChainHeight()); m_wallet->refresh(); if (!m_synchronized) { m_synchronized = true; @@ -1079,7 +1076,8 @@ void WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction m_wallet->init(daemon_address, upper_transaction_size_limit); // in case new wallet, this will force fast-refresh (pulling hashes instead of blocks) - if (isNewWallet()) { + // If daemon isn't synced a calculated block height will be used instead + if (isNewWallet() && daemonSynced()) { m_wallet->set_refresh_from_block_height(daemonBlockChainHeight()); } diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index 4ee5ab8df..6b48caf1d 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -32,6 +32,7 @@ #include "wallet_manager.h" #include "wallet.h" #include "common_defines.h" +#include "common/dns_utils.h" #include "net/http_client.h" #include <boost/filesystem.hpp> @@ -137,7 +138,7 @@ void WalletManagerImpl::setDaemonAddress(const std::string &address) m_daemonAddress = address; } -bool WalletManagerImpl::connected(uint32_t *version = NULL) const +bool WalletManagerImpl::connected(uint32_t *version) const { epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_VERSION::request> req_t = AUTO_VAL_INIT(req_t); epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_VERSION::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); @@ -352,9 +353,40 @@ double WalletManagerImpl::miningHashRate() const return mres.speed; } +void WalletManagerImpl::hardForkInfo(uint8_t &version, uint64_t &earliest_height) const +{ + epee::json_rpc::request<cryptonote::COMMAND_RPC_HARD_FORK_INFO::request> req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response<cryptonote::COMMAND_RPC_HARD_FORK_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + + version = 0; + earliest_height = 0; + + epee::net_utils::http::http_simple_client http_client; + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "hard_fork_info"; + req_t.params.version = 0; + bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemonAddress + "/json_rpc", req_t, resp_t, http_client); + if (!r || resp_t.result.status != CORE_RPC_STATUS_OK) + return; + version = resp_t.result.version; + earliest_height = resp_t.result.earliest_height; +} + +uint64_t WalletManagerImpl::blockTarget() const +{ + cryptonote::COMMAND_RPC_GET_INFO::request ireq; + cryptonote::COMMAND_RPC_GET_INFO::response ires; + + epee::net_utils::http::http_simple_client http_client; + if (!epee::net_utils::invoke_http_json_remote_command2(m_daemonAddress + "/getinfo", ireq, ires, http_client)) + return 0; + return ires.target; +} + std::string WalletManagerImpl::resolveOpenAlias(const std::string &address, bool &dnssec_valid) const { - std::vector<std::string> addresses = tools::wallet2::addresses_from_url(address, dnssec_valid); + std::vector<std::string> addresses = tools::dns_utils::addresses_from_url(address, dnssec_valid); if (addresses.empty()) return ""; return addresses.front(); diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 214afc3fa..01752f69b 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -46,12 +46,14 @@ public: std::vector<std::string> findWallets(const std::string &path); std::string errorString() const; void setDaemonAddress(const std::string &address); - bool connected(uint32_t *version) const; + bool connected(uint32_t *version = NULL) const; bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const; uint64_t blockchainHeight() const; uint64_t blockchainTargetHeight() const; uint64_t networkDifficulty() const; double miningHashRate() const; + void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const; + uint64_t blockTarget() const; std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const; private: diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 221dd8e0b..1550787e5 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -51,7 +51,6 @@ using namespace epee; #include "cryptonote_protocol/blobdatatype.h" #include "mnemonics/electrum-words.h" #include "common/i18n.h" -#include "common/dns_utils.h" #include "common/util.h" #include "rapidjson/document.h" #include "rapidjson/writer.h" @@ -77,8 +76,8 @@ using namespace cryptonote; // arbitrary, used to generate different hashes from the same input #define CHACHA8_KEY_TAIL 0x8c -#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\002" -#define SIGNED_TX_PREFIX "Monero signed tx set\002" +#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003" +#define SIGNED_TX_PREFIX "Monero signed tx set\003" #define RECENT_OUTPUT_RATIO (0.25) // 25% of outputs are from the recent zone #define RECENT_OUTPUT_ZONE (5 * 86400) // last 5 days are the recent zone @@ -1613,6 +1612,9 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re // and then fall through to regular refresh processing } + // If stop() is called during fast refresh we don't need to continue + if(!m_run.load(std::memory_order_relaxed)) + return; pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices); // always reset start_height to 0 to force short_chain_ history to be used on // subsequent pulls in this refresh. @@ -1632,7 +1634,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re process_blocks(blocks_start_height, blocks, o_indices, added_blocks); blocks_fetched += added_blocks; pull_thread.join(); - if(!added_blocks) + if(blocks_start_height == next_blocks_start_height) break; // switch to the new blocks from the daemon @@ -1668,7 +1670,9 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re try { - update_pool_state(); + // If stop() is called we don't need to check pending transactions + if(m_run.load(std::memory_order_relaxed)) + update_pool_state(); } catch (...) { @@ -2139,7 +2143,7 @@ void wallet2::rewrite(const std::string& wallet_name, const std::string& passwor prepare_file_names(wallet_name); boost::system::error_code ignored_ec; THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file); - bool r = store_keys(m_keys_file, password, false); + bool r = store_keys(m_keys_file, password, m_watch_only); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); } /*! @@ -2854,74 +2858,7 @@ std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts( return retVal; } } // anonymous namespace - -/** - * @brief gets a monero address from the TXT record of a DNS entry - * - * gets the monero address from the TXT record of the DNS entry associated - * with <url>. If this lookup fails, or the TXT record does not contain an - * XMR address in the correct format, returns an empty string. <dnssec_valid> - * will be set true or false according to whether or not the DNS query passes - * DNSSEC validation. - * - * @param url the url to look up - * @param dnssec_valid return-by-reference for DNSSEC status of query - * - * @return a monero address (as a string) or an empty string - */ -std::vector<std::string> wallet2::addresses_from_url(const std::string& url, bool& dnssec_valid) -{ - std::vector<std::string> addresses; - // get txt records - bool dnssec_available, dnssec_isvalid; - std::string oa_addr = tools::DNSResolver::instance().get_dns_format_from_oa_address(url); - auto records = tools::DNSResolver::instance().get_txt_record(oa_addr, dnssec_available, dnssec_isvalid); - - // TODO: update this to allow for conveying that dnssec was not available - if (dnssec_available && dnssec_isvalid) - { - dnssec_valid = true; - } - else dnssec_valid = false; - - // for each txt record, try to find a monero address in it. - for (auto& rec : records) - { - std::string addr = address_from_txt_record(rec); - if (addr.size()) - { - addresses.push_back(addr); - } - } - - return addresses; -} - //---------------------------------------------------------------------------------------------------- -// TODO: parse the string in a less stupid way, probably with regex -std::string wallet2::address_from_txt_record(const std::string& s) -{ - // make sure the txt record has "oa1:xmr" and find it - auto pos = s.find("oa1:xmr"); - - // search from there to find "recipient_address=" - pos = s.find("recipient_address=", pos); - - pos += 18; // move past "recipient_address=" - - // find the next semicolon - auto pos2 = s.find(";", pos); - if (pos2 != std::string::npos) - { - // length of address == 95, we can at least validate that much here - if (pos2 - pos == 95) - { - return s.substr(pos, 95); - } - } - return std::string(); -} - crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const { std::vector<tx_extra_field> tx_extra_fields; @@ -3018,14 +2955,19 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri for (auto &tx: ptx_vector) txs.txes.push_back(tx.construction_data); txs.transfers = m_transfers; - std::string s = obj_to_json_str(txs); - if (s.empty()) - return false; - LOG_PRINT_L2("Saving unsigned tx data: " << s); - // save as binary as there's no implementation of loading a json_archive - if (!::serialization::dump_binary(txs, s)) + // save as binary + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << txs; + } + catch (...) + { return false; - return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + s); + } + LOG_PRINT_L2("Saving unsigned tx data: " << oss.str()); + return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + oss.str()); } //---------------------------------------------------------------------------------------------------- bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, std::function<bool(const unsigned_tx_set&)> accept_func) @@ -3050,7 +2992,14 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s return false; } unsigned_tx_set exported_txs; - if (!::serialization::parse_binary(std::string(s.c_str() + magiclen, s.size() - magiclen), exported_txs)) + s = s.substr(magiclen); + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> exported_txs; + } + catch (...) { LOG_PRINT_L0("Failed to parse data from " << unsigned_filename); return false; @@ -3123,14 +3072,19 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s signed_txes.key_images[i] = m_transfers[i].m_key_image; } - s = obj_to_json_str(signed_txes); - if (s.empty()) - return false; - LOG_PRINT_L2("Saving signed tx data: " << s); - // save as binary as there's no implementation of loading a json_archive - if (!::serialization::dump_binary(signed_txes, s)) + // save as binary + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << signed_txes; + } + catch(...) + { return false; - return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + s); + } + LOG_PRINT_L2("Saving signed tx data: " << oss.str()); + return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + oss.str()); } //---------------------------------------------------------------------------------------------------- bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func) @@ -3156,7 +3110,14 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal LOG_PRINT_L0("Bad magic from " << signed_filename); return false; } - if (!::serialization::parse_binary(std::string(s.c_str() + magiclen, s.size() - magiclen), signed_txs)) + s = s.substr(magiclen); + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> signed_txs; + } + catch (...) { LOG_PRINT_L0("Failed to parse data from " << signed_filename); return false; @@ -3521,6 +3482,23 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si outs.back().reserve(fake_outputs_count + 1); const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); + // make sure the real outputs we asked for are really included, along + // with the correct key and mask: this guards against an active attack + // where the node sends dummy data for all outputs, and we then send + // the real one, which the node can then tell from the fake outputs, + // as it has different data than the dummy data it had sent earlier + bool real_out_found = false; + for (size_t n = 0; n < requested_outputs_count; ++n) + { + size_t i = base + n; + if (req.outputs[i].index == td.m_global_output_index) + if (daemon_resp.outs[i].key == boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key) + if (daemon_resp.outs[i].mask == mask) + real_out_found = true; + } + THROW_WALLET_EXCEPTION_IF(!real_out_found, error::wallet_internal_error, + "Daemon response did not include the requested real output"); + // pick real out first (it will be sorted when done) outs.back().push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask)); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 54e26008b..e1eafbae3 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -58,6 +58,8 @@ #include <iostream> #define WALLET_RCP_CONNECTION_TIMEOUT 200000 +class Serialization_portability_wallet_Test; + namespace tools { class i_wallet2_callback @@ -86,6 +88,7 @@ namespace tools class wallet2 { + friend class ::Serialization_portability_wallet_Test; public: enum RefreshType { RefreshFull, @@ -201,17 +204,6 @@ namespace tools uint64_t unlock_time; bool use_rct; std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change - - BEGIN_SERIALIZE_OBJECT() - FIELD(sources) - FIELD(change_dts) - FIELD(splitted_dsts) - FIELD(selected_transfers) - FIELD(extra) - VARINT_FIELD(unlock_time) - FIELD(use_rct) - FIELD(dests) - END_SERIALIZE() }; typedef std::vector<transfer_details> transfer_container; @@ -232,39 +224,18 @@ namespace tools std::vector<cryptonote::tx_destination_entry> dests; tx_construction_data construction_data; - - BEGIN_SERIALIZE_OBJECT() - FIELD(tx) - VARINT_FIELD(dust) - VARINT_FIELD(fee) - FIELD(dust_added_to_fee) - FIELD(change_dts) - FIELD(selected_transfers) - FIELD(key_images) - FIELD(tx_key) - FIELD(dests) - FIELD(construction_data) - END_SERIALIZE() }; struct unsigned_tx_set { std::vector<tx_construction_data> txes; wallet2::transfer_container transfers; - BEGIN_SERIALIZE_OBJECT() - FIELD(txes) - FIELD(transfers) - END_SERIALIZE() }; struct signed_tx_set { std::vector<pending_tx> ptx; std::vector<crypto::key_image> key_images; - BEGIN_SERIALIZE_OBJECT() - FIELD(ptx) - FIELD(key_images) - END_SERIALIZE() }; struct keys_file_data @@ -503,10 +474,6 @@ namespace tools static bool parse_short_payment_id(const std::string& payment_id_str, crypto::hash8& payment_id); static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); - static std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid); - - static std::string address_from_txt_record(const std::string& s); - bool always_confirm_transfers() const { return m_always_confirm_transfers; } void always_confirm_transfers(bool always) { m_always_confirm_transfers = always; } bool store_tx_info() const { return m_store_tx_info; } @@ -672,6 +639,10 @@ BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 6) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 3) BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 16) +BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) +BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0) +BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 0) +BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 0) namespace boost { @@ -869,6 +840,48 @@ namespace boost a & x.m_payment_id; a & x.m_description; } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver) + { + a & x.txes; + a & x.transfers; + } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::signed_tx_set &x, const boost::serialization::version_type ver) + { + a & x.ptx; + a & x.key_images; + } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::tx_construction_data &x, const boost::serialization::version_type ver) + { + a & x.sources; + a & x.change_dts; + a & x.splitted_dsts; + a & x.selected_transfers; + a & x.extra; + a & x.unlock_time; + a & x.use_rct; + a & x.dests; + } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::pending_tx &x, const boost::serialization::version_type ver) + { + a & x.tx; + a & x.dust; + a & x.fee; + a & x.dust_added_to_fee; + a & x.change_dts; + a & x.selected_transfers; + a & x.key_images; + a & x.tx_key; + a & x.dests; + a & x.construction_data; + } } } diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 2e1d95b58..e5707ad09 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -175,6 +175,7 @@ struct AddressBook virtual void refresh() = 0; virtual std::string errorString() const = 0; virtual int errorCode() const = 0; + virtual int lookupPaymentID(const std::string &payment_id) const = 0; }; struct WalletListener @@ -562,6 +563,12 @@ struct WalletManager //! returns current mining hash rate (0 if not mining) virtual double miningHashRate() const = 0; + //! returns current hard fork info + virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0; + + //! returns current block target + virtual uint64_t blockTarget() const = 0; + //! resolves an OpenAlias address to a monero address virtual std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const = 0; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 65eab49d7..60b7a4dba 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -62,6 +62,14 @@ else () FOLDER "${folder}") endif () +file(COPY + data/wallet_9svHk1.keys + data/wallet_9svHk1 + data/outputs + data/unsigned_monero_tx + data/signed_monero_tx + DESTINATION data) + add_subdirectory(core_tests) add_subdirectory(crypto) add_subdirectory(functional_tests) diff --git a/tests/data/outputs b/tests/data/outputs Binary files differnew file mode 100644 index 000000000..abb351c29 --- /dev/null +++ b/tests/data/outputs diff --git a/tests/data/signed_monero_tx b/tests/data/signed_monero_tx Binary files differnew file mode 100644 index 000000000..f1aa1ee8a --- /dev/null +++ b/tests/data/signed_monero_tx diff --git a/tests/data/unsigned_monero_tx b/tests/data/unsigned_monero_tx Binary files differnew file mode 100644 index 000000000..4e644bc45 --- /dev/null +++ b/tests/data/unsigned_monero_tx diff --git a/tests/data/wallet_9svHk1 b/tests/data/wallet_9svHk1 Binary files differnew file mode 100644 index 000000000..a49b6b50e --- /dev/null +++ b/tests/data/wallet_9svHk1 diff --git a/tests/data/wallet_9svHk1.keys b/tests/data/wallet_9svHk1.keys Binary files differnew file mode 100644 index 000000000..3159e200b --- /dev/null +++ b/tests/data/wallet_9svHk1.keys diff --git a/tests/unit_tests/address_from_url.cpp b/tests/unit_tests/address_from_url.cpp index ff163dab9..ad3aca6b4 100644 --- a/tests/unit_tests/address_from_url.cpp +++ b/tests/unit_tests/address_from_url.cpp @@ -31,6 +31,7 @@ #include "gtest/gtest.h" #include "wallet/wallet2.h" +#include "common/dns_utils.h" #include <string> TEST(AddressFromTXT, Success) @@ -42,7 +43,7 @@ TEST(AddressFromTXT, Success) txtr += addr; txtr += ";"; - std::string res = tools::wallet2::address_from_txt_record(txtr); + std::string res = tools::dns_utils::address_from_txt_record(txtr); EXPECT_STREQ(addr.c_str(), res.c_str()); @@ -52,7 +53,7 @@ TEST(AddressFromTXT, Success) txtr2 += "more foobar"; - res = tools::wallet2::address_from_txt_record(txtr2); + res = tools::dns_utils::address_from_txt_record(txtr2); EXPECT_STREQ(addr.c_str(), res.c_str()); @@ -61,7 +62,7 @@ TEST(AddressFromTXT, Success) txtr3 += addr; txtr3 += "; foobar"; - res = tools::wallet2::address_from_txt_record(txtr3); + res = tools::dns_utils::address_from_txt_record(txtr3); EXPECT_STREQ(addr.c_str(), res.c_str()); } @@ -70,13 +71,13 @@ TEST(AddressFromTXT, Failure) { std::string txtr = "oa1:xmr recipient_address=not a real address"; - std::string res = tools::wallet2::address_from_txt_record(txtr); + std::string res = tools::dns_utils::address_from_txt_record(txtr); ASSERT_STREQ("", res.c_str()); txtr += ";"; - res = tools::wallet2::address_from_txt_record(txtr); + res = tools::dns_utils::address_from_txt_record(txtr); ASSERT_STREQ("", res.c_str()); } @@ -86,7 +87,7 @@ TEST(AddressFromURL, Success) bool dnssec_result = false; - std::vector<std::string> addresses = tools::wallet2::addresses_from_url("donate.getmonero.org", dnssec_result); + std::vector<std::string> addresses = tools::dns_utils::addresses_from_url("donate.getmonero.org", dnssec_result); EXPECT_EQ(1, addresses.size()); if (addresses.size() == 1) @@ -95,7 +96,7 @@ TEST(AddressFromURL, Success) } // OpenAlias address with an @ instead of first . - addresses = tools::wallet2::addresses_from_url("donate@getmonero.org", dnssec_result); + addresses = tools::dns_utils::addresses_from_url("donate@getmonero.org", dnssec_result); EXPECT_EQ(1, addresses.size()); if (addresses.size() == 1) { @@ -107,7 +108,7 @@ TEST(AddressFromURL, Failure) { bool dnssec_result = false; - std::vector<std::string> addresses = tools::wallet2::addresses_from_url("example.invalid", dnssec_result); + std::vector<std::string> addresses = tools::dns_utils::addresses_from_url("example.invalid", dnssec_result); // for a non-existing domain such as "example.invalid", the non-existence is proved with NSEC records ASSERT_TRUE(dnssec_result); diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp index be2885f45..ca72f5e0e 100644 --- a/tests/unit_tests/hardfork.cpp +++ b/tests/unit_tests/hardfork.cpp @@ -78,6 +78,7 @@ public: virtual bool tx_exists(const crypto::hash& h, uint64_t& tx_index) const { return false; } virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const { return 0; } virtual transaction get_tx(const crypto::hash& h) const { return transaction(); } + virtual bool get_tx(const crypto::hash& h, transaction &tx) const { return false; } virtual uint64_t get_tx_count() const { return 0; } virtual std::vector<transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const { return std::vector<transaction>(); } virtual uint64_t get_tx_block_height(const crypto::hash& h) const { return 0; } diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 70757dfc1..2be7bde8c 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -34,6 +34,7 @@ #include <iostream> #include <vector> #include <boost/foreach.hpp> +#include <boost/archive/portable_binary_iarchive.hpp> #include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/cryptonote_basic_impl.h" #include "ringct/rctSigs.h" @@ -44,6 +45,7 @@ #include "serialization/variant.h" #include "serialization/vector.h" #include "serialization/binary_utils.h" +#include "wallet/wallet2.h" #include "gtest/gtest.h" using namespace std; @@ -649,3 +651,514 @@ TEST(Serialization, serializes_ringct_types) ASSERT_TRUE(serialization::dump_binary(tx1, blob2)); ASSERT_TRUE(blob == blob2); } + +TEST(Serialization, portability_wallet) +{ + const bool testnet = true; + const bool restricted = false; + tools::wallet2 w(testnet, restricted); + string wallet_file = "../data/wallet_9svHk1"; + string password = "test"; + bool r = false; + try + { + w.load(wallet_file, password); + r = true; + } + catch (const exception& e) + {} + ASSERT_TRUE(r); + /* + fields of tools::wallet2 to be checked: + std::vector<crypto::hash> m_blockchain + std::vector<transfer_details> m_transfers // TODO + cryptonote::account_public_address m_account_public_address + std::unordered_map<crypto::key_image, size_t> m_key_images + std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs + std::unordered_multimap<crypto::hash, payment_details> m_payments + std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys + std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs + std::unordered_map<crypto::hash, std::string> m_tx_notes + std::unordered_map<crypto::hash, payment_details> m_unconfirmed_payments + std::unordered_map<crypto::public_key, size_t> m_pub_keys + std::vector<tools::wallet2::address_book_row> m_address_book + */ + // blockchain + ASSERT_TRUE(w.m_blockchain.size() == 1); + ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_blockchain[0]) == "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b"); + // transfers (TODO) + ASSERT_TRUE(w.m_transfers.size() == 3); + // account public address + ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_account_public_address.m_view_public_key) == "e47d4b6df6ab7339539148c2a03ad3e2f3434e5ab2046848e1f21369a3937cad"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_account_public_address.m_spend_public_key) == "13daa2af00ad26a372d317195de0bdd716f7a05d33bc4d7aff1664b6ee93c060"); + // key images + ASSERT_TRUE(w.m_key_images.size() == 3); + { + crypto::key_image ki[3]; + epee::string_tools::hex_to_pod("c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8", ki[0]); + epee::string_tools::hex_to_pod("d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0", ki[1]); + epee::string_tools::hex_to_pod("6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76", ki[2]); + ASSERT_TRUE(w.m_key_images.find(ki[0])->second == 0); + ASSERT_TRUE(w.m_key_images.find(ki[1])->second == 1); + ASSERT_TRUE(w.m_key_images.find(ki[2])->second == 2); + } + // unconfirmed txs + ASSERT_TRUE(w.m_unconfirmed_txs.size() == 0); + // payments + ASSERT_TRUE(w.m_payments.size() == 2); + { + auto pd0 = w.m_payments.begin(); + auto pd1 = pd0; + ++pd1; + ASSERT_TRUE(epee::string_tools::pod_to_hex(pd0->first) == "0000000000000000000000000000000000000000000000000000000000000000"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(pd1->first) == "0000000000000000000000000000000000000000000000000000000000000000"); + if (epee::string_tools::pod_to_hex(pd0->second.m_tx_hash) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc") + swap(pd0, pd1); + ASSERT_TRUE(epee::string_tools::pod_to_hex(pd0->second.m_tx_hash) == "15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(pd1->second.m_tx_hash) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc"); + ASSERT_TRUE(pd0->second.m_amount == 13400845012231); + ASSERT_TRUE(pd1->second.m_amount == 1200000000000); + ASSERT_TRUE(pd0->second.m_block_height == 818424); + ASSERT_TRUE(pd1->second.m_block_height == 818522); + ASSERT_TRUE(pd0->second.m_unlock_time == 818484); + ASSERT_TRUE(pd1->second.m_unlock_time == 0); + ASSERT_TRUE(pd0->second.m_timestamp == 1483263366); + ASSERT_TRUE(pd1->second.m_timestamp == 1483272963); + } + // tx keys + ASSERT_TRUE(w.m_tx_keys.size() == 2); + { + auto tx_key0 = w.m_tx_keys.begin(); + auto tx_key1 = tx_key0; + ++tx_key1; + if (epee::string_tools::pod_to_hex(tx_key0->first) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba") + swap(tx_key0, tx_key1); + ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key0->first) == "b9aac8c020ab33859e0c0b6331f46a8780d349e7ac17b067116e2d87bf48daad"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key1->first) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key0->second) == "bf3614c6de1d06c09add5d92a5265d8c76af706f7bc6ac830d6b0d109aa87701"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(tx_key1->second) == "e556884246df5a787def6732c6ea38f1e092fa13e5ea98f732b99c07a6332003"); + } + // confirmed txs + ASSERT_TRUE(w.m_confirmed_txs.size() == 1); + // tx notes + ASSERT_TRUE(w.m_tx_notes.size() == 2); + { + crypto::hash h[2]; + epee::string_tools::hex_to_pod("15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e", h[0]); + epee::string_tools::hex_to_pod("6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba", h[1]); + ASSERT_TRUE(w.m_tx_notes.find(h[0])->second == "sample note"); + ASSERT_TRUE(w.m_tx_notes.find(h[1])->second == "sample note 2"); + } + // unconfirmed payments + ASSERT_TRUE(w.m_unconfirmed_payments.size() == 0); + // pub keys + ASSERT_TRUE(w.m_pub_keys.size() == 3); + { + crypto::public_key pubkey[3]; + epee::string_tools::hex_to_pod("33f75f264574cb3a9ea5b24220a5312e183d36dc321c9091dfbb720922a4f7b0", pubkey[0]); + epee::string_tools::hex_to_pod("5066ff2ce9861b1d131cf16eeaa01264933a49f28242b97b153e922ec7b4b3cb", pubkey[1]); + epee::string_tools::hex_to_pod("0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8", pubkey[2]); + ASSERT_TRUE(w.m_pub_keys.find(pubkey[0])->second == 0); + ASSERT_TRUE(w.m_pub_keys.find(pubkey[1])->second == 1); + ASSERT_TRUE(w.m_pub_keys.find(pubkey[2])->second == 2); + } + // address book + ASSERT_TRUE(w.m_address_book.size() == 1); + { + auto address_book_row = w.m_address_book.begin(); + ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_spend_public_key) == "9bc53a6ff7b0831c9470f71b6b972dbe5ad1e8606f72682868b1dda64e119fb3"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_view_public_key) == "49fece1ef97dc0c0f7a5e2106e75e96edd910f7e86b56e1e308cd0cf734df191"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_payment_id) == "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); + ASSERT_TRUE(address_book_row->m_description == "testnet wallet 9y52S6"); + } +} + +#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" +TEST(Serialization, portability_outputs) +{ + // read file + const std::string filename = "../data/outputs"; + std::string data; + bool r = epee::file_io_utils::load_file_to_string(filename, data); + ASSERT_TRUE(r); + const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC); + ASSERT_FALSE(data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen)); + // decrypt (copied from wallet2::decrypt) + auto decrypt = [] (const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) -> string + { + const size_t prefix_size = sizeof(chacha8_iv) + (authenticated ? sizeof(crypto::signature) : 0); + if(ciphertext.size() < prefix_size) + return {}; + crypto::chacha8_key key; + crypto::generate_chacha8_key(&skey, sizeof(skey), key); + const crypto::chacha8_iv &iv = *(const crypto::chacha8_iv*)&ciphertext[0]; + std::string plaintext; + plaintext.resize(ciphertext.size() - prefix_size); + if (authenticated) + { + crypto::hash hash; + crypto::cn_fast_hash(ciphertext.data(), ciphertext.size() - sizeof(signature), hash); + crypto::public_key pkey; + crypto::secret_key_to_public_key(skey, pkey); + const crypto::signature &signature = *(const crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)]; + if(!crypto::check_signature(hash, pkey, signature)) + return {}; + } + crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); + return std::move(plaintext); + }; + crypto::secret_key view_secret_key; + epee::string_tools::hex_to_pod("339673bb1187e2f73ba7841ab6841c5553f96e9f13f8fe6612e69318db4e9d0a", view_secret_key); + bool authenticated = true; + data = decrypt(std::string(data, magiclen), view_secret_key, authenticated); + ASSERT_FALSE(data.empty()); + // check public view/spend keys + const size_t headerlen = 2 * sizeof(crypto::public_key); + ASSERT_FALSE(data.size() < headerlen); + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; + ASSERT_TRUE(epee::string_tools::pod_to_hex(public_spend_key) == "13daa2af00ad26a372d317195de0bdd716f7a05d33bc4d7aff1664b6ee93c060"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(public_view_key) == "e47d4b6df6ab7339539148c2a03ad3e2f3434e5ab2046848e1f21369a3937cad"); + r = false; + std::vector<tools::wallet2::transfer_details> outputs; + try + { + std::istringstream iss(std::string(data, headerlen)); + boost::archive::portable_binary_iarchive ar(iss); + ar >> outputs; + r = true; + } + catch (...) + {} + ASSERT_TRUE(r); + /* + fields of tools::wallet2::transfer_details to be checked: + uint64_t m_block_height + cryptonote::transaction_prefix m_tx // TODO + crypto::hash m_txid + size_t m_internal_output_index + uint64_t m_global_output_index + bool m_spent + uint64_t m_spent_height + crypto::key_image m_key_image + rct::key m_mask + uint64_t m_amount + bool m_rct + bool m_key_image_known + size_t m_pk_index + */ + ASSERT_TRUE(outputs.size() == 3); + auto& td0 = outputs[0]; + auto& td1 = outputs[1]; + auto& td2 = outputs[2]; + ASSERT_TRUE(td0.m_block_height == 818424); + ASSERT_TRUE(td1.m_block_height == 818522); + ASSERT_TRUE(td2.m_block_height == 818522); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_txid) == "15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_txid) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_txid) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba"); + ASSERT_TRUE(td0.m_internal_output_index == 0); + ASSERT_TRUE(td1.m_internal_output_index == 0); + ASSERT_TRUE(td2.m_internal_output_index == 1); + ASSERT_TRUE(td0.m_global_output_index == 19642); + ASSERT_TRUE(td1.m_global_output_index == 19757); + ASSERT_TRUE(td2.m_global_output_index == 19760); + ASSERT_TRUE (td0.m_spent); + ASSERT_FALSE(td1.m_spent); + ASSERT_FALSE(td2.m_spent); + ASSERT_TRUE(td0.m_spent_height == 0); + ASSERT_TRUE(td1.m_spent_height == 0); + ASSERT_TRUE(td2.m_spent_height == 0); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_key_image) == "c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_key_image) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_key_image) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_mask) == "0100000000000000000000000000000000000000000000000000000000000000"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_mask) == "d3997a7b27fa199a377643b88cbd3f20f447496746dabe92d288730ecaeda007"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703"); + ASSERT_TRUE(td0.m_amount == 13400845012231); + ASSERT_TRUE(td1.m_amount == 1200000000000); + ASSERT_TRUE(td2.m_amount == 11066009260865); + ASSERT_TRUE(td0.m_rct); + ASSERT_TRUE(td1.m_rct); + ASSERT_TRUE(td2.m_rct); + ASSERT_TRUE(td0.m_key_image_known); + ASSERT_TRUE(td1.m_key_image_known); + ASSERT_TRUE(td2.m_key_image_known); + ASSERT_TRUE(td0.m_pk_index == 0); + ASSERT_TRUE(td1.m_pk_index == 0); + ASSERT_TRUE(td2.m_pk_index == 0); +} + +#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003" +TEST(Serialization, portability_unsigned_tx) +{ + const string filename = "../data/unsigned_monero_tx"; + std::string s; + const bool testnet = true; + bool r = epee::file_io_utils::load_file_to_string(filename, s); + ASSERT_TRUE(r); + const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); + ASSERT_FALSE(strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen)); + tools::wallet2::unsigned_tx_set exported_txs; + s = s.substr(magiclen); + r = false; + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> exported_txs; + r = true; + } + catch (...) + {} + ASSERT_TRUE(r); + /* + fields of tools::wallet2::unsigned_tx_set to be checked: + std::vector<tx_construction_data> txes + std::vector<wallet2::transfer_details> m_transfers + + fields of toolw::wallet2::tx_construction_data to be checked: + std::vector<cryptonote::tx_source_entry> sources + cryptonote::tx_destination_entry change_dts + std::vector<cryptonote::tx_destination_entry> splitted_dsts + std::list<size_t> selected_transfers + std::vector<uint8_t> extra + uint64_t unlock_time + bool use_rct + std::vector<cryptonote::tx_destination_entry> dests + + fields of cryptonote::tx_source_entry to be checked: + std::vector<std::pair<uint64_t, rct::ctkey>> outputs + size_t real_output + crypto::public_key real_out_tx_key + size_t real_output_in_tx_index + uint64_t amount + bool rct + rct::key mask + + fields of cryptonote::tx_destination_entry to be checked: + uint64_t amount + account_public_address addr + */ + // txes + ASSERT_TRUE(exported_txs.txes.size() == 1); + auto& tcd = exported_txs.txes[0]; + // tcd.sources + ASSERT_TRUE(tcd.sources.size() == 1); + auto& tse = tcd.sources[0]; + // tcd.sources[0].outputs + ASSERT_TRUE(tse.outputs.size() == 5); + auto& out0 = tse.outputs[0]; + auto& out1 = tse.outputs[1]; + auto& out2 = tse.outputs[2]; + auto& out3 = tse.outputs[3]; + auto& out4 = tse.outputs[4]; + ASSERT_TRUE(out0.first == 6295); + ASSERT_TRUE(out1.first == 14302); + ASSERT_TRUE(out2.first == 17598); + ASSERT_TRUE(out3.first == 18671); + ASSERT_TRUE(out4.first == 19760); + ASSERT_TRUE(epee::string_tools::pod_to_hex(out0.second) == "e7272cb589954ddeedd20de9411ed57265f154d41f33cec9ff69e5d642e09814096490b0ac85308342acf436cc0270d53abef9dc04c6202f2459e879bfd40ce6"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(out1.second) == "c3a9f49d1fe75939cc3feb39871ce0a7366c2879a63faa1a5cf34e65723b120a272ff0c7d84ab8b6ee3528d196450b0e28b3fed276bc2597a2b5b17afb9354ab"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(out2.second) == "176e239c8c39000c2275e2f63ed7d55c55e0843524091522bbd3d3b869044969021fad70fc1244115449d4754829ae7c47346342ee5d52a2cdd47dfc351d0ab0"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(out3.second) == "ef12d7946302fb064f2ba9df1a73d72233ac74664ed3b370580fa3bdc377542ad93f64898bd95851d6efe0d7bf2dbbea9b7c6b3c57e2c807e7b17d55b4622259"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(out4.second) == "0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8525096cbc88d00a841eed66f3cdb6f0a018e6ce9fb9433ed61afba15cbbebd04"); + // tcd.sources[0].{real_output, real_out_tx_key, real_output_in_tx_index, amount, rct, mask} + ASSERT_TRUE(tse.real_output == 4); + ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.real_out_tx_key) == "4d86c7ba1c285fe4bc1cd7b54ba894fa89fa02fc6b0bbeea67d53251acd14a05"); + ASSERT_TRUE(tse.real_output_in_tx_index == 1); + ASSERT_TRUE(tse.amount == 11066009260865); + ASSERT_TRUE(tse.rct); + ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703"); + // tcd.change_dts + ASSERT_TRUE(tcd.change_dts.amount == 9631208773403); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, tcd.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); + // tcd.splitted_dsts + ASSERT_TRUE(tcd.splitted_dsts.size() == 2); + auto& splitted_dst0 = tcd.splitted_dsts[0]; + auto& splitted_dst1 = tcd.splitted_dsts[1]; + ASSERT_TRUE(splitted_dst0.amount == 1400000000000); + ASSERT_TRUE(splitted_dst1.amount == 9631208773403); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst0.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst1.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); + // tcd.selected_transfers + ASSERT_TRUE(tcd.selected_transfers.size() == 1); + ASSERT_TRUE(tcd.selected_transfers.front() == 2); + // tcd.extra + ASSERT_TRUE(tcd.extra.size() == 68); + string tcd_extra_str = epee::string_tools::buff_to_hex(string(reinterpret_cast<char*>(tcd.extra.data()), tcd.extra.size())); + ASSERT_TRUE(tcd_extra_str == "0x2 0x21 0x0 0xf8 0xd 0xbc 0xfc 0xa2 0x2d 0x84 0x1e 0xa0 0x46 0x18 0x7a 0x5b 0x19 0xea 0x4d 0xd1 0xa2 0x8a 0x58 0xa8 0x72 0x9 0xd5 0xdf 0x2 0x30 0x60 0xac 0x9e 0x48 0x84 0x1 0xb2 0xfd 0x5d 0x4e 0x45 0x8b 0xf1 0x28 0xa0 0xc8 0x30 0xd1 0x35 0x4f 0x47 0xb9 0xed 0xc9 0x82 0x8c 0x83 0x37 0x7d 0xb6 0xb5 0xe5 0x3d 0xff 0x64 0xb0 0xde 0x7f "); + // tcd.{unlock_time, use_rct} + ASSERT_TRUE(tcd.unlock_time == 0); + ASSERT_TRUE(tcd.use_rct); + // tcd.dests + ASSERT_TRUE(tcd.dests.size() == 1); + auto& dest = tcd.dests[0]; + ASSERT_TRUE(dest.amount == 1400000000000); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, dest.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); + // transfers + ASSERT_TRUE(exported_txs.transfers.size() == 3); + auto& td0 = exported_txs.transfers[0]; + auto& td1 = exported_txs.transfers[1]; + auto& td2 = exported_txs.transfers[2]; + ASSERT_TRUE(td0.m_block_height == 818424); + ASSERT_TRUE(td1.m_block_height == 818522); + ASSERT_TRUE(td2.m_block_height == 818522); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_txid) == "15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_txid) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_txid) == "6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba"); + ASSERT_TRUE(td0.m_internal_output_index == 0); + ASSERT_TRUE(td1.m_internal_output_index == 0); + ASSERT_TRUE(td2.m_internal_output_index == 1); + ASSERT_TRUE(td0.m_global_output_index == 19642); + ASSERT_TRUE(td1.m_global_output_index == 19757); + ASSERT_TRUE(td2.m_global_output_index == 19760); + ASSERT_TRUE (td0.m_spent); + ASSERT_FALSE(td1.m_spent); + ASSERT_FALSE(td2.m_spent); + ASSERT_TRUE(td0.m_spent_height == 0); + ASSERT_TRUE(td1.m_spent_height == 0); + ASSERT_TRUE(td2.m_spent_height == 0); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_key_image) == "c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_key_image) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_key_image) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td0.m_mask) == "0100000000000000000000000000000000000000000000000000000000000000"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td1.m_mask) == "d3997a7b27fa199a377643b88cbd3f20f447496746dabe92d288730ecaeda007"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(td2.m_mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703"); + ASSERT_TRUE(td0.m_amount == 13400845012231); + ASSERT_TRUE(td1.m_amount == 1200000000000); + ASSERT_TRUE(td2.m_amount == 11066009260865); + ASSERT_TRUE(td0.m_rct); + ASSERT_TRUE(td1.m_rct); + ASSERT_TRUE(td2.m_rct); + ASSERT_TRUE(td0.m_key_image_known); + ASSERT_TRUE(td1.m_key_image_known); + ASSERT_TRUE(td2.m_key_image_known); + ASSERT_TRUE(td0.m_pk_index == 0); + ASSERT_TRUE(td1.m_pk_index == 0); + ASSERT_TRUE(td2.m_pk_index == 0); +} + +#define SIGNED_TX_PREFIX "Monero signed tx set\003" +TEST(Serialization, portability_signed_tx) +{ + const string filename = "../data/signed_monero_tx"; + const bool testnet = true; + std::string s; + bool r = epee::file_io_utils::load_file_to_string(filename, s); + ASSERT_TRUE(r); + const size_t magiclen = strlen(SIGNED_TX_PREFIX); + ASSERT_FALSE(strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen)); + tools::wallet2::signed_tx_set exported_txs; + s = s.substr(magiclen); + r = false; + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> exported_txs; + r = true; + } + catch (...) + {} + ASSERT_TRUE(r); + /* + fields of tools::wallet2::signed_tx_set to be checked: + std::vector<pending_tx> ptx + std::vector<crypto::key_image> key_images + + fields of tools::walllet2::pending_tx to be checked: + cryptonote::transaction tx // TODO + uint64_t dust + uint64_t fee + bool dust_added_to_fee + cryptonote::tx_destination_entry change_dts + std::list<size_t> selected_transfers + std::string key_images + crypto::secret_key tx_key + std::vector<cryptonote::tx_destination_entry> dests + tx_construction_data construction_data + */ + // ptx + ASSERT_TRUE(exported_txs.ptx.size() == 1); + auto& ptx = exported_txs.ptx[0]; + // ptx.{dust, fee, dust_added_to_fee} + ASSERT_TRUE (ptx.dust == 0); + ASSERT_TRUE (ptx.fee == 34800487462); + ASSERT_FALSE(ptx.dust_added_to_fee); + // ptx.change.{amount, addr} + ASSERT_TRUE(ptx.change_dts.amount == 9631208773403); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, ptx.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); + // ptx.selected_transfers + ASSERT_TRUE(ptx.selected_transfers.size() == 1); + ASSERT_TRUE(ptx.selected_transfers.front() == 2); + // ptx.{key_images, tx_key} + ASSERT_TRUE(ptx.key_images == "<6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76> "); + ASSERT_TRUE(epee::string_tools::pod_to_hex(ptx.tx_key) == "0100000000000000000000000000000000000000000000000000000000000000"); + // ptx.dests + ASSERT_TRUE(ptx.dests.size() == 1); + ASSERT_TRUE(ptx.dests[0].amount == 1400000000000); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, ptx.dests[0].addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); + // ptx.construction_data + auto& tcd = ptx.construction_data; + ASSERT_TRUE(tcd.sources.size() == 1); + auto& tse = tcd.sources[0]; + // ptx.construction_data.sources[0].outputs + ASSERT_TRUE(tse.outputs.size() == 5); + auto& out0 = tse.outputs[0]; + auto& out1 = tse.outputs[1]; + auto& out2 = tse.outputs[2]; + auto& out3 = tse.outputs[3]; + auto& out4 = tse.outputs[4]; + ASSERT_TRUE(out0.first == 6295); + ASSERT_TRUE(out1.first == 14302); + ASSERT_TRUE(out2.first == 17598); + ASSERT_TRUE(out3.first == 18671); + ASSERT_TRUE(out4.first == 19760); + ASSERT_TRUE(epee::string_tools::pod_to_hex(out0.second) == "e7272cb589954ddeedd20de9411ed57265f154d41f33cec9ff69e5d642e09814096490b0ac85308342acf436cc0270d53abef9dc04c6202f2459e879bfd40ce6"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(out1.second) == "c3a9f49d1fe75939cc3feb39871ce0a7366c2879a63faa1a5cf34e65723b120a272ff0c7d84ab8b6ee3528d196450b0e28b3fed276bc2597a2b5b17afb9354ab"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(out2.second) == "176e239c8c39000c2275e2f63ed7d55c55e0843524091522bbd3d3b869044969021fad70fc1244115449d4754829ae7c47346342ee5d52a2cdd47dfc351d0ab0"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(out3.second) == "ef12d7946302fb064f2ba9df1a73d72233ac74664ed3b370580fa3bdc377542ad93f64898bd95851d6efe0d7bf2dbbea9b7c6b3c57e2c807e7b17d55b4622259"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(out4.second) == "0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8525096cbc88d00a841eed66f3cdb6f0a018e6ce9fb9433ed61afba15cbbebd04"); + // ptx.construction_data.sources[0].{real_output, real_out_tx_key, real_output_in_tx_index, amount, rct, mask} + ASSERT_TRUE(tse.real_output == 4); + ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.real_out_tx_key) == "4d86c7ba1c285fe4bc1cd7b54ba894fa89fa02fc6b0bbeea67d53251acd14a05"); + ASSERT_TRUE(tse.real_output_in_tx_index == 1); + ASSERT_TRUE(tse.amount == 11066009260865); + ASSERT_TRUE(tse.rct); + ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703"); + // ptx.construction_data.change_dts + ASSERT_TRUE(tcd.change_dts.amount == 9631208773403); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, tcd.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); + // ptx.construction_data.splitted_dsts + ASSERT_TRUE(tcd.splitted_dsts.size() == 2); + auto& splitted_dst0 = tcd.splitted_dsts[0]; + auto& splitted_dst1 = tcd.splitted_dsts[1]; + ASSERT_TRUE(splitted_dst0.amount == 1400000000000); + ASSERT_TRUE(splitted_dst1.amount == 9631208773403); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst0.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst1.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); + // ptx.construction_data.selected_transfers + ASSERT_TRUE(tcd.selected_transfers.size() == 1); + ASSERT_TRUE(tcd.selected_transfers.front() == 2); + // ptx.construction_data.extra + ASSERT_TRUE(tcd.extra.size() == 68); + string tcd_extra_str = epee::string_tools::buff_to_hex(string(reinterpret_cast<char*>(tcd.extra.data()), tcd.extra.size())); + ASSERT_TRUE(tcd_extra_str == "0x2 0x21 0x0 0xf8 0xd 0xbc 0xfc 0xa2 0x2d 0x84 0x1e 0xa0 0x46 0x18 0x7a 0x5b 0x19 0xea 0x4d 0xd1 0xa2 0x8a 0x58 0xa8 0x72 0x9 0xd5 0xdf 0x2 0x30 0x60 0xac 0x9e 0x48 0x84 0x1 0xb2 0xfd 0x5d 0x4e 0x45 0x8b 0xf1 0x28 0xa0 0xc8 0x30 0xd1 0x35 0x4f 0x47 0xb9 0xed 0xc9 0x82 0x8c 0x83 0x37 0x7d 0xb6 0xb5 0xe5 0x3d 0xff 0x64 0xb0 0xde 0x7f "); + // ptx.construction_data.{unlock_time, use_rct} + ASSERT_TRUE(tcd.unlock_time == 0); + ASSERT_TRUE(tcd.use_rct); + // ptx.construction_data.dests + ASSERT_TRUE(tcd.dests.size() == 1); + auto& dest = tcd.dests[0]; + ASSERT_TRUE(dest.amount == 1400000000000); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, dest.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); + // key_images + ASSERT_TRUE(exported_txs.key_images.size() == 3); + auto& ki0 = exported_txs.key_images[0]; + auto& ki1 = exported_txs.key_images[1]; + auto& ki2 = exported_txs.key_images[2]; + ASSERT_TRUE(epee::string_tools::pod_to_hex(ki0) == "c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(ki1) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(ki2) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76"); +} |