aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--contrib/epee/include/net/levin_base.h1
-rw-r--r--contrib/epee/include/net/levin_client_async.h12
-rw-r--r--contrib/epee/include/net/levin_protocol_handler.h2
-rw-r--r--contrib/epee/include/net/levin_protocol_handler_async.h16
-rw-r--r--contrib/epee/tests/src/net/test_net.h2
-rw-r--r--external/db_drivers/liblmdb/mdb.c10
-rw-r--r--external/easylogging++/CMakeLists.txt3
-rw-r--r--src/blockchain_db/blockchain_db.h7
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp45
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h4
-rw-r--r--src/common/command_line.cpp14
-rw-r--r--src/common/command_line.h2
-rw-r--r--src/common/util.cpp7
-rw-r--r--src/common/util.h3
-rw-r--r--src/cryptonote_core/blockchain.cpp20
-rw-r--r--src/cryptonote_core/blockchain.h4
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp16
-rw-r--r--src/cryptonote_core/cryptonote_core.h14
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp25
-rw-r--r--src/cryptonote_core/tx_pool.cpp110
-rw-r--r--src/cryptonote_core/tx_pool.h32
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl9
-rw-r--r--src/daemon/rpc_command_executor.cpp10
-rw-r--r--src/p2p/net_node.inl2
-rwxr-xr-xsrc/rpc/core_rpc_server.cpp55
-rw-r--r--src/rpc/core_rpc_server.h10
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h8
-rw-r--r--src/rpc/message_data_structs.h1
-rw-r--r--src/serialization/json_object.cpp2
-rw-r--r--src/simplewallet/simplewallet.cpp157
-rw-r--r--src/simplewallet/simplewallet.h1
-rw-r--r--src/wallet/api/transaction_history.cpp6
-rw-r--r--src/wallet/wallet2.cpp110
-rw-r--r--src/wallet/wallet2.h39
-rwxr-xr-xsrc/wallet/wallet_rpc_server.cpp14
-rw-r--r--src/wallet/wallet_rpc_server.h2
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h2
-rw-r--r--tests/CMakeLists.txt2
-rw-r--r--tests/core_proxy/core_proxy.cpp2
-rw-r--r--tests/core_tests/CMakeLists.txt10
-rw-r--r--tests/core_tests/block_reward.cpp2
-rw-r--r--tests/core_tests/block_validation.cpp2
-rw-r--r--tests/core_tests/chain_split_1.cpp2
-rw-r--r--tests/core_tests/chain_switch_1.cpp2
-rw-r--r--tests/core_tests/chaingen_main.cpp1
-rw-r--r--tests/core_tests/double_spend.cpp2
-rw-r--r--tests/core_tests/integer_overflow.cpp2
-rw-r--r--tests/core_tests/rct.cpp2
-rw-r--r--tests/core_tests/ring_signature_1.cpp2
-rw-r--r--tests/core_tests/tx_validation.cpp2
-rw-r--r--tests/core_tests/v2_tests.cpp2
-rw-r--r--tests/functional_tests/main.cpp2
-rw-r--r--tests/fuzz/fuzzer.cpp2
-rw-r--r--tests/libwallet_api_tests/main.cpp2
-rw-r--r--tests/net_load_tests/clt.cpp10
-rw-r--r--tests/net_load_tests/net_load_tests.h5
-rw-r--r--tests/net_load_tests/srv.cpp5
-rw-r--r--tests/performance_tests/main.cpp2
-rw-r--r--tests/unit_tests/CMakeLists.txt1
-rw-r--r--tests/unit_tests/epee_levin_protocol_handler_async.cpp8
-rw-r--r--tests/unit_tests/hardfork.cpp4
-rw-r--r--tests/unit_tests/main.cpp2
-rw-r--r--tests/unit_tests/subaddress.cpp118
-rw-r--r--utils/gpg_keys/stoffu.asc62
65 files changed, 806 insertions, 231 deletions
diff --git a/README.md b/README.md
index a392cfe95..db72bd079 100644
--- a/README.md
+++ b/README.md
@@ -212,7 +212,7 @@ invokes cmake commands as needed.
make release-test
- *NOTE*: `coretests` test may take a few hours to complete.
+ *NOTE*: `core_tests` test may take a few hours to complete.
* **Optional**: to build binaries suitable for debugging:
diff --git a/contrib/epee/include/net/levin_base.h b/contrib/epee/include/net/levin_base.h
index d630bff19..7d060f5ef 100644
--- a/contrib/epee/include/net/levin_base.h
+++ b/contrib/epee/include/net/levin_base.h
@@ -87,6 +87,7 @@ namespace levin
virtual void on_connection_new(t_connection_context& context){};
virtual void on_connection_close(t_connection_context& context){};
+ virtual ~levin_commands_handler(){}
};
#define LEVIN_OK 0
diff --git a/contrib/epee/include/net/levin_client_async.h b/contrib/epee/include/net/levin_client_async.h
index 337d345c4..6c8f9bcb3 100644
--- a/contrib/epee/include/net/levin_client_async.h
+++ b/contrib/epee/include/net/levin_client_async.h
@@ -52,6 +52,7 @@ namespace levin
class levin_client_async
{
levin_commands_handler* m_pcommands_handler;
+ void (*commands_handler_destroy)(levin_commands_handler*);
volatile uint32_t m_is_stop;
volatile uint32_t m_threads_count;
::critical_section m_send_lock;
@@ -85,9 +86,9 @@ namespace levin
::critical_section m_connection_lock;
net_utils::blocked_mode_client m_transport;
public:
- levin_client_async():m_pcommands_handler(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0)
+ levin_client_async():m_pcommands_handler(NULL), commands_handler_destroy(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0)
{}
- levin_client_async(const levin_client_async& /*v*/):m_pcommands_handler(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0)
+ levin_client_async(const levin_client_async& /*v*/):m_pcommands_handler(NULL), commands_handler_destroy(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0)
{}
~levin_client_async()
{
@@ -97,11 +98,16 @@ namespace levin
while(boost::interprocess::ipcdetail::atomic_read32(&m_threads_count))
::Sleep(100);
+
+ set_handler(NULL);
}
- void set_handler(levin_commands_handler* phandler)
+ void set_handler(levin_commands_handler* phandler, void (*destroy)(levin_commands_handler*) = NULL)
{
+ if (commands_handler_destroy && m_pcommands_handler)
+ (*commands_handler_destroy)(m_pcommands_handler);
m_pcommands_handler = phandler;
+ m_pcommands_handler_destroy = destroy;
}
bool connect(uint32_t ip, uint32_t port, uint32_t timeout)
diff --git a/contrib/epee/include/net/levin_protocol_handler.h b/contrib/epee/include/net/levin_protocol_handler.h
index fbc9727e2..b3a75bedc 100644
--- a/contrib/epee/include/net/levin_protocol_handler.h
+++ b/contrib/epee/include/net/levin_protocol_handler.h
@@ -43,6 +43,8 @@ namespace levin
struct protocl_handler_config
{
levin_commands_handler<t_connection_context>* m_pcommands_handler;
+ void (*m_pcommands_handler_destroy)(levin_commands_handler<t_connection_context>*);
+ ~protocl_handler_config() { if (m_pcommands_handler && m_pcommands_handler_destroy) (*m_pcommands_handler_destroy)(m_pcommands_handler); }
};
template<class t_connection_context = net_utils::connection_context_base>
diff --git a/contrib/epee/include/net/levin_protocol_handler_async.h b/contrib/epee/include/net/levin_protocol_handler_async.h
index 779f4e78f..7ad6d198b 100644
--- a/contrib/epee/include/net/levin_protocol_handler_async.h
+++ b/contrib/epee/include/net/levin_protocol_handler_async.h
@@ -72,9 +72,11 @@ class async_protocol_handler_config
friend class async_protocol_handler<t_connection_context>;
+ levin_commands_handler<t_connection_context>* m_pcommands_handler;
+ void (*m_pcommands_handler_destroy)(levin_commands_handler<t_connection_context>*);
+
public:
typedef t_connection_context connection_context;
- levin_commands_handler<t_connection_context>* m_pcommands_handler;
uint64_t m_max_packet_size;
uint64_t m_invoke_timeout;
@@ -91,8 +93,9 @@ public:
template<class callback_t>
bool for_connection(const boost::uuids::uuid &connection_id, callback_t cb);
size_t get_connections_count();
+ void set_handler(levin_commands_handler<t_connection_context>* handler, void (*destroy)(levin_commands_handler<t_connection_context>*) = NULL);
- async_protocol_handler_config():m_pcommands_handler(NULL), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE)
+ async_protocol_handler_config():m_pcommands_handler(NULL), m_pcommands_handler_destroy(NULL), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE)
{}
void del_out_connections(size_t count);
};
@@ -832,6 +835,15 @@ size_t async_protocol_handler_config<t_connection_context>::get_connections_coun
}
//------------------------------------------------------------------------------------------
template<class t_connection_context>
+void async_protocol_handler_config<t_connection_context>::set_handler(levin_commands_handler<t_connection_context>* handler, void (*destroy)(levin_commands_handler<t_connection_context>*))
+{
+ if (m_pcommands_handler && m_pcommands_handler_destroy)
+ (*m_pcommands_handler_destroy)(m_pcommands_handler);
+ m_pcommands_handler = handler;
+ m_pcommands_handler_destroy = destroy;
+}
+//------------------------------------------------------------------------------------------
+template<class t_connection_context>
int async_protocol_handler_config<t_connection_context>::notify(int command, const std::string& in_buff, boost::uuids::uuid connection_id)
{
async_protocol_handler<t_connection_context>* aph;
diff --git a/contrib/epee/tests/src/net/test_net.h b/contrib/epee/tests/src/net/test_net.h
index 5b21036bb..2e1b1e5fd 100644
--- a/contrib/epee/tests/src/net/test_net.h
+++ b/contrib/epee/tests/src/net/test_net.h
@@ -155,7 +155,7 @@ namespace tests
bool init(const std::string& bind_port = "", const std::string& bind_ip = "0.0.0.0")
{
- m_net_server.get_config_object().m_pcommands_handler = this;
+ m_net_server.get_config_object().set_handler(this);
m_net_server.get_config_object().m_invoke_timeout = 1000;
LOG_PRINT_L0("Binding on " << bind_ip << ":" << bind_port);
return m_net_server.init_server(bind_port, bind_ip);
diff --git a/external/db_drivers/liblmdb/mdb.c b/external/db_drivers/liblmdb/mdb.c
index b3de9702f..87b244ce7 100644
--- a/external/db_drivers/liblmdb/mdb.c
+++ b/external/db_drivers/liblmdb/mdb.c
@@ -304,7 +304,8 @@ union semun {
# else
# define MDB_USE_ROBUST 1
/* glibc < 2.12 only provided _np API */
-# if defined(__GLIBC__) && GLIBC_VER < 0x02000c
+# if (defined(__GLIBC__) && GLIBC_VER < 0x02000c) || \
+ (defined(PTHREAD_MUTEX_ROBUST_NP) && !defined(PTHREAD_MUTEX_ROBUST))
# define PTHREAD_MUTEX_ROBUST PTHREAD_MUTEX_ROBUST_NP
# define pthread_mutexattr_setrobust(attr, flag) pthread_mutexattr_setrobust_np(attr, flag)
# define pthread_mutex_consistent(mutex) pthread_mutex_consistent_np(mutex)
@@ -4977,6 +4978,13 @@ mdb_env_setup_locks(MDB_env *env, char *lpath, int mode, int *excl)
#else /* MDB_USE_POSIX_MUTEX: */
pthread_mutexattr_t mattr;
+ /* Solaris needs this before initing a robust mutex. Otherwise
+ * it may skip the init and return EBUSY "seems someone already
+ * inited" or EINVAL "it was inited differently".
+ */
+ memset(env->me_txns->mti_rmutex, 0, sizeof(*env->me_txns->mti_rmutex));
+ memset(env->me_txns->mti_wmutex, 0, sizeof(*env->me_txns->mti_wmutex));
+
if ((rc = pthread_mutexattr_init(&mattr))
|| (rc = pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED))
#ifdef MDB_ROBUST_SUPPORTED
diff --git a/external/easylogging++/CMakeLists.txt b/external/easylogging++/CMakeLists.txt
index ae7d931cf..97d0bf571 100644
--- a/external/easylogging++/CMakeLists.txt
+++ b/external/easylogging++/CMakeLists.txt
@@ -51,6 +51,7 @@ if (BUILD_GUI_DEPS)
set(lib_folder lib)
endif()
install(TARGETS easylogging
- ARCHIVE DESTINATION ${lib_folder})
+ ARCHIVE DESTINATION ${lib_folder}
+ LIBRARY DESTINATION ${lib_folder})
endif()
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 838385e8a..79676b808 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -147,8 +147,9 @@ struct txpool_tx_meta_t
uint8_t kept_by_block;
uint8_t relayed;
uint8_t do_not_relay;
+ uint8_t double_spend_seen: 1;
- uint8_t padding[77]; // till 192 bytes
+ uint8_t padding[76]; // till 192 bytes
};
#define DBF_SAFE 1
@@ -1314,7 +1315,7 @@ public:
/**
* @brief get the number of transactions in the txpool
*/
- virtual uint64_t get_txpool_tx_count() const = 0;
+ virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const = 0;
/**
* @brief check whether a txid is in the txpool
@@ -1369,7 +1370,7 @@ public:
*
* @return false if the function returns false for any transaction, otherwise true
*/
- virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false) const = 0;
+ virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = true) const = 0;
/**
* @brief runs a function over all key images stored
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 5bd02bcf7..6ebb35639 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -1522,21 +1522,49 @@ void BlockchainLMDB::update_txpool_tx(const crypto::hash &txid, const txpool_tx_
}
}
-uint64_t BlockchainLMDB::get_txpool_tx_count() const
+uint64_t BlockchainLMDB::get_txpool_tx_count(bool include_unrelayed_txes) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
- TXN_PREFIX_RDONLY();
int result;
+ uint64_t num_entries = 0;
- MDB_stat db_stats;
- if ((result = mdb_stat(m_txn, m_txpool_meta, &db_stats)))
- throw0(DB_ERROR(lmdb_error("Failed to query m_txpool_meta: ", result).c_str()));
+ TXN_PREFIX_RDONLY();
+
+ if (include_unrelayed_txes)
+ {
+ // No filtering, we can get the number of tx the "fast" way
+ MDB_stat db_stats;
+ if ((result = mdb_stat(m_txn, m_txpool_meta, &db_stats)))
+ throw0(DB_ERROR(lmdb_error("Failed to query m_txpool_meta: ", result).c_str()));
+ num_entries = db_stats.ms_entries;
+ }
+ else
+ {
+ // Filter unrelayed tx out of the result, so we need to loop over transactions and check their meta data
+ RCURSOR(txpool_meta);
+ RCURSOR(txpool_blob);
+ MDB_val k;
+ MDB_val v;
+ MDB_cursor_op op = MDB_FIRST;
+ while (1)
+ {
+ result = mdb_cursor_get(m_cur_txpool_meta, &k, &v, op);
+ op = MDB_NEXT;
+ if (result == MDB_NOTFOUND)
+ break;
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to enumerate txpool tx metadata: ", result).c_str()));
+ const txpool_tx_meta_t &meta = *(const txpool_tx_meta_t*)v.mv_data;
+ if (!meta.do_not_relay)
+ ++num_entries;
+ }
+ }
TXN_POSTFIX_RDONLY();
- return db_stats.ms_entries;
+ return num_entries;
}
bool BlockchainLMDB::txpool_has_tx(const crypto::hash& txid) const
@@ -1633,7 +1661,7 @@ cryptonote::blobdata BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid
return bd;
}
-bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob) const
+bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, bool include_unrelayed_txes) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
@@ -1657,6 +1685,9 @@ bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&,
throw0(DB_ERROR(lmdb_error("Failed to enumerate txpool tx metadata: ", result).c_str()));
const crypto::hash txid = *(const crypto::hash*)k.mv_data;
const txpool_tx_meta_t &meta = *(const txpool_tx_meta_t*)v.mv_data;
+ if (!include_unrelayed_txes && meta.do_not_relay)
+ // Skipping that tx
+ continue;
const cryptonote::blobdata *passed_bd = NULL;
cryptonote::blobdata bd;
if (include_blob)
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index 98571a7f8..fce8f29ed 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -243,13 +243,13 @@ public:
virtual void add_txpool_tx(const transaction &tx, const txpool_tx_meta_t& meta);
virtual void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t& meta);
- virtual uint64_t get_txpool_tx_count() const;
+ virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const;
virtual bool txpool_has_tx(const crypto::hash &txid) const;
virtual void remove_txpool_tx(const crypto::hash& txid);
virtual txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const;
virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const;
virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const;
- virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false) const;
+ virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false, bool include_unrelayed_txes = true) const;
virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const;
virtual bool for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const;
diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp
index 666b3267f..d4a28fc85 100644
--- a/src/common/command_line.cpp
+++ b/src/common/command_line.cpp
@@ -78,6 +78,20 @@ namespace command_line
return false;
}
+ bool is_no(const std::string& str)
+ {
+ if (str == "n" || str == "N")
+ return true;
+
+ boost::algorithm::is_iequal ignore_case{};
+ if (boost::algorithm::equals("no", str, ignore_case))
+ return true;
+ if (boost::algorithm::equals(command_line::tr("no"), str, ignore_case))
+ return true;
+
+ return false;
+ }
+
const arg_descriptor<bool> arg_help = {"help", "Produce help message"};
const arg_descriptor<bool> arg_version = {"version", "Output version information"};
const arg_descriptor<std::string> arg_data_dir = {"data-dir", "Specify data directory"};
diff --git a/src/common/command_line.h b/src/common/command_line.h
index 04fd70be5..bfc8b19c6 100644
--- a/src/common/command_line.h
+++ b/src/common/command_line.h
@@ -45,6 +45,8 @@ namespace command_line
//! \return True if `str` is `is_iequal("y" || "yes" || `tr("yes"))`.
bool is_yes(const std::string& str);
+ //! \return True if `str` is `is_iequal("n" || "no" || `tr("no"))`.
+ bool is_no(const std::string& str);
template<typename T, bool required = false>
struct arg_descriptor;
diff --git a/src/common/util.cpp b/src/common/util.cpp
index 30746f680..1e180d325 100644
--- a/src/common/util.cpp
+++ b/src/common/util.cpp
@@ -549,6 +549,13 @@ std::string get_nix_version_display_string()
if (!strcmp(ver, "2.25"))
MCLOG_RED(el::Level::Warning, "global", "Running with glibc " << ver << ", hangs may occur - change glibc version if possible");
#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000
+ SSL_library_init();
+#else
+ OPENSSL_init_ssl(0, NULL);
+#endif
+
return true;
}
void set_strict_default_file_permissions(bool strict)
diff --git a/src/common/util.h b/src/common/util.h
index 2e4d6e917..1aac026c1 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -148,9 +148,10 @@ namespace tools
}
return r;
#else
- /* Only blocks SIGINT and SIGTERM */
+ /* Only blocks SIGINT, SIGTERM and SIGPIPE */
signal(SIGINT, posix_handler);
signal(SIGTERM, posix_handler);
+ signal(SIGPIPE, SIG_IGN);
m_handler = t;
return true;
#endif
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 9d89c6280..4e1ab8a48 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -3166,9 +3166,9 @@ bool Blockchain::flush_txes_from_pool(const std::list<crypto::hash> &txids)
cryptonote::transaction tx;
size_t blob_size;
uint64_t fee;
- bool relayed, do_not_relay;
+ bool relayed, do_not_relay, double_spend_seen;
MINFO("Removing txid " << txid << " from the pool");
- if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed, do_not_relay))
+ if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed, do_not_relay, double_spend_seen))
{
MERROR("Failed to remove txid " << txid << " from the pool");
res = false;
@@ -3351,7 +3351,7 @@ leave:
transaction tx;
size_t blob_size = 0;
uint64_t fee = 0;
- bool relayed = false, do_not_relay = false;
+ bool relayed = false, do_not_relay = false, double_spend_seen = false;
TIME_MEASURE_START(aa);
// XXX old code does not check whether tx exists
@@ -3368,7 +3368,7 @@ leave:
TIME_MEASURE_START(bb);
// get transaction with hash <tx_id> from tx_pool
- if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed, do_not_relay))
+ if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed, do_not_relay, double_spend_seen))
{
MERROR_VER("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id);
bvc.m_verifivation_failed = true;
@@ -4211,9 +4211,9 @@ void Blockchain::remove_txpool_tx(const crypto::hash &txid)
m_db->remove_txpool_tx(txid);
}
-uint64_t Blockchain::get_txpool_tx_count() const
+uint64_t Blockchain::get_txpool_tx_count(bool include_unrelayed_txes) const
{
- return m_db->get_txpool_tx_count();
+ return m_db->get_txpool_tx_count(include_unrelayed_txes);
}
txpool_tx_meta_t Blockchain::get_txpool_tx_meta(const crypto::hash& txid) const
@@ -4231,9 +4231,9 @@ cryptonote::blobdata Blockchain::get_txpool_tx_blob(const crypto::hash& txid) co
return m_db->get_txpool_tx_blob(txid);
}
-bool Blockchain::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob) const
+bool Blockchain::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, bool include_unrelayed_txes) const
{
- return m_db->for_all_txpool_txes(f, include_blob);
+ return m_db->for_all_txpool_txes(f, include_blob, include_unrelayed_txes);
}
void Blockchain::set_user_options(uint64_t maxthreads, uint64_t blocks_per_sync, blockchain_db_sync_mode sync_mode, bool fast_sync)
@@ -4381,12 +4381,12 @@ void Blockchain::load_compiled_in_block_hashes()
size_t blob_size;
uint64_t fee;
- bool relayed, do_not_relay;
+ bool relayed, do_not_relay, double_spend_seen;
transaction pool_tx;
for(const transaction &tx : txs)
{
crypto::hash tx_hash = get_transaction_hash(tx);
- m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed, do_not_relay);
+ m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed, do_not_relay, double_spend_seen);
}
}
}
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index f64bd35e3..3f2930fb0 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -947,11 +947,11 @@ namespace cryptonote
void add_txpool_tx(transaction &tx, const txpool_tx_meta_t &meta);
void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t &meta);
void remove_txpool_tx(const crypto::hash &txid);
- uint64_t get_txpool_tx_count() const;
+ uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const;
txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const;
bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const;
cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const;
- bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false) const;
+ bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = true) const;
bool is_within_compiled_block_hash_area(uint64_t height) const;
bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); }
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 61f844612..3f56ffac7 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -1182,21 +1182,21 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
- bool core::get_pool_transactions(std::list<transaction>& txs) const
+ bool core::get_pool_transactions(std::list<transaction>& txs, bool include_sensitive_data) const
{
- m_mempool.get_transactions(txs);
+ m_mempool.get_transactions(txs, include_sensitive_data);
return true;
}
//-----------------------------------------------------------------------------------------------
- bool core::get_pool_transaction_hashes(std::vector<crypto::hash>& txs) const
+ bool core::get_pool_transaction_hashes(std::vector<crypto::hash>& txs, bool include_sensitive_data) const
{
- m_mempool.get_transaction_hashes(txs);
+ m_mempool.get_transaction_hashes(txs, include_sensitive_data);
return true;
}
//-----------------------------------------------------------------------------------------------
- bool core::get_pool_transaction_stats(struct txpool_stats& stats) const
+ bool core::get_pool_transaction_stats(struct txpool_stats& stats, bool include_sensitive_data) const
{
- m_mempool.get_transaction_stats(stats);
+ m_mempool.get_transaction_stats(stats, include_sensitive_data);
return true;
}
//-----------------------------------------------------------------------------------------------
@@ -1210,9 +1210,9 @@ namespace cryptonote
return m_mempool.have_tx(id);
}
//-----------------------------------------------------------------------------------------------
- bool core::get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const
+ bool core::get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_sensitive_data) const
{
- return m_mempool.get_transactions_and_spent_keys_info(tx_infos, key_image_infos);
+ return m_mempool.get_transactions_and_spent_keys_info(tx_infos, key_image_infos, include_sensitive_data);
}
//-----------------------------------------------------------------------------------------------
bool core::get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index 7340e1024..a3d47280a 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -420,11 +420,12 @@ namespace cryptonote
/**
* @copydoc tx_memory_pool::get_transactions
+ * @param include_unrelayed_txes include unrelayed txes in result
*
* @note see tx_memory_pool::get_transactions
*/
- bool get_pool_transactions(std::list<transaction>& txs) const;
-
+ bool get_pool_transactions(std::list<transaction>& txs, bool include_unrelayed_txes = true) const;
+
/**
* @copydoc tx_memory_pool::get_txpool_backlog
*
@@ -434,17 +435,19 @@ namespace cryptonote
/**
* @copydoc tx_memory_pool::get_transactions
+ * @param include_unrelayed_txes include unrelayed txes in result
*
* @note see tx_memory_pool::get_transactions
*/
- bool get_pool_transaction_hashes(std::vector<crypto::hash>& txs) const;
+ bool get_pool_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes = true) const;
/**
* @copydoc tx_memory_pool::get_transactions
+ * @param include_unrelayed_txes include unrelayed txes in result
*
* @note see tx_memory_pool::get_transactions
*/
- bool get_pool_transaction_stats(struct txpool_stats& stats) const;
+ bool get_pool_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes = true) const;
/**
* @copydoc tx_memory_pool::get_transaction
@@ -455,10 +458,11 @@ namespace cryptonote
/**
* @copydoc tx_memory_pool::get_pool_transactions_and_spent_keys_info
+ * @param include_unrelayed_txes include unrelayed txes in result
*
* @note see tx_memory_pool::get_pool_transactions_and_spent_keys_info
*/
- bool get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const;
+ bool get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_unrelayed_txes = true) const;
/**
* @copydoc tx_memory_pool::get_pool_for_rpc
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index 96f6ee872..9409a60a5 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -160,12 +160,6 @@ namespace cryptonote
//---------------------------------------------------------------
bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct)
{
- if (destinations.empty())
- {
- LOG_ERROR("The destinations must be non-empty");
- return false;
- }
-
std::vector<rct::key> amount_keys;
tx.set_null();
amount_keys.clear();
@@ -174,9 +168,8 @@ namespace cryptonote
tx.unlock_time = unlock_time;
tx.extra = extra;
- keypair txkey = keypair::generate();
- remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key));
- add_tx_pub_key_to_extra(tx, txkey.pub);
+ keypair txkey;
+ txkey.sec = rct::rct2sk(rct::skGen());
tx_key = txkey.sec;
// if we have a stealth payment id, find it and encrypt it with the tx key now
@@ -323,9 +316,13 @@ namespace cryptonote
if (num_stdaddresses == 0 && num_subaddresses == 1)
{
txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(txkey.sec)));
- remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key));
- add_tx_pub_key_to_extra(tx, txkey.pub);
}
+ else
+ {
+ txkey.pub = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(txkey.sec)));
+ }
+ remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key));
+ add_tx_pub_key_to_extra(tx, txkey.pub);
std::vector<crypto::public_key> additional_tx_public_keys;
additional_tx_keys.clear();
@@ -348,9 +345,11 @@ namespace cryptonote
keypair additional_txkey;
if (need_additional_txkeys)
{
- additional_txkey = keypair::generate();
+ additional_txkey.sec = rct::rct2sk(rct::skGen());
if (dst_entr.is_subaddress)
additional_txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(dst_entr.addr.m_spend_public_key), rct::sk2rct(additional_txkey.sec)));
+ else
+ additional_txkey.pub = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(additional_txkey.sec)));
}
bool r;
@@ -393,7 +392,6 @@ namespace cryptonote
}
remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys));
- add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys);
LOG_PRINT_L2("tx pubkey: " << txkey.pub);
if (need_additional_txkeys)
@@ -401,6 +399,7 @@ namespace cryptonote
LOG_PRINT_L2("additional tx pubkeys: ");
for (size_t i = 0; i < additional_tx_public_keys.size(); ++i)
LOG_PRINT_L2(additional_tx_public_keys[i]);
+ add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys);
}
//check money
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index 9071c330c..6bfcfe529 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -187,6 +187,7 @@ namespace cryptonote
{
if(have_tx_keyimges_as_spent(tx))
{
+ mark_double_spend(tx);
LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images");
tvc.m_verifivation_failed = true;
tvc.m_double_spend = true;
@@ -228,6 +229,7 @@ namespace cryptonote
meta.last_relayed_time = time(NULL);
meta.relayed = relayed;
meta.do_not_relay = do_not_relay;
+ meta.double_spend_seen = have_tx_keyimges_as_spent(tx);
memset(meta.padding, 0, sizeof(meta.padding));
try
{
@@ -266,6 +268,7 @@ namespace cryptonote
meta.last_relayed_time = time(NULL);
meta.relayed = relayed;
meta.do_not_relay = do_not_relay;
+ meta.double_spend_seen = false;
memset(meta.padding, 0, sizeof(meta.padding));
try
@@ -354,7 +357,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay)
+ bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen)
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
@@ -377,6 +380,7 @@ namespace cryptonote
fee = meta.fee;
relayed = meta.relayed;
do_not_relay = meta.do_not_relay;
+ double_spend_seen = meta.double_spend_seen;
// remove first, in case this throws, so key images aren't removed
m_blockchain.remove_txpool_tx(id);
@@ -432,7 +436,7 @@ namespace cryptonote
remove.insert(txid);
}
return true;
- });
+ }, false);
if (!remove.empty())
{
@@ -494,7 +498,7 @@ namespace cryptonote
}
}
return true;
- });
+ }, false);
return true;
}
//---------------------------------------------------------------------------------
@@ -521,14 +525,14 @@ namespace cryptonote
}
}
//---------------------------------------------------------------------------------
- size_t tx_memory_pool::get_transactions_count() const
+ size_t tx_memory_pool::get_transactions_count(bool include_unrelayed_txes) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
- return m_blockchain.get_txpool_tx_count();
+ return m_blockchain.get_txpool_tx_count(include_unrelayed_txes);
}
//---------------------------------------------------------------------------------
- void tx_memory_pool::get_transactions(std::list<transaction>& txs) const
+ void tx_memory_pool::get_transactions(std::list<transaction>& txs, bool include_unrelayed_txes) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
@@ -542,20 +546,20 @@ namespace cryptonote
}
txs.push_back(tx);
return true;
- }, true);
+ }, true, include_unrelayed_txes);
}
//------------------------------------------------------------------
- void tx_memory_pool::get_transaction_hashes(std::vector<crypto::hash>& txs) const
+ void tx_memory_pool::get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
m_blockchain.for_all_txpool_txes([&txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){
txs.push_back(txid);
return true;
- });
+ }, false, include_unrelayed_txes);
}
//------------------------------------------------------------------
- void tx_memory_pool::get_transaction_backlog(std::vector<tx_backlog_entry>& backlog) const
+ void tx_memory_pool::get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_unrelayed_txes) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
@@ -563,16 +567,16 @@ namespace cryptonote
m_blockchain.for_all_txpool_txes([&backlog, now](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){
backlog.push_back({meta.blob_size, meta.fee, meta.receive_time - now});
return true;
- });
+ }, false, include_unrelayed_txes);
}
//------------------------------------------------------------------
- void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats) const
+ void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
const uint64_t now = time(NULL);
std::map<uint64_t, txpool_histo> agebytes;
- stats.txs_total = m_blockchain.get_txpool_tx_count();
+ stats.txs_total = m_blockchain.get_txpool_tx_count(include_unrelayed_txes);
std::vector<uint32_t> sizes;
sizes.reserve(stats.txs_total);
m_blockchain.for_all_txpool_txes([&stats, &sizes, now, &agebytes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){
@@ -594,8 +598,10 @@ namespace cryptonote
uint64_t age = now - meta.receive_time + (now == meta.receive_time);
agebytes[age].txs++;
agebytes[age].bytes += meta.blob_size;
+ if (meta.double_spend_seen)
+ ++stats.num_double_spends;
return true;
- });
+ }, false, include_unrelayed_txes);
stats.bytes_med = epee::misc_utils::median(sizes);
if (stats.txs_total > 1)
{
@@ -642,13 +648,14 @@ namespace cryptonote
}
//------------------------------------------------------------------
//TODO: investigate whether boolean return is appropriate
- bool tx_memory_pool::get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const
+ bool tx_memory_pool::get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_sensitive_data) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
- m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){
+ m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos, include_sensitive_data](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){
tx_info txi;
txi.id_hash = epee::string_tools::pod_to_hex(txid);
+ txi.tx_blob = *bd;
transaction tx;
if (!parse_and_validate_tx_from_blob(*bd, tx))
{
@@ -664,14 +671,18 @@ namespace cryptonote
txi.max_used_block_id_hash = epee::string_tools::pod_to_hex(meta.max_used_block_id);
txi.last_failed_height = meta.last_failed_height;
txi.last_failed_id_hash = epee::string_tools::pod_to_hex(meta.last_failed_id);
- txi.receive_time = meta.receive_time;
+ // In restricted mode we do not include this data:
+ txi.receive_time = include_sensitive_data ? meta.receive_time : 0;
txi.relayed = meta.relayed;
- txi.last_relayed_time = meta.last_relayed_time;
+ // In restricted mode we do not include this data:
+ txi.last_relayed_time = include_sensitive_data ? meta.last_relayed_time : 0;
txi.do_not_relay = meta.do_not_relay;
+ txi.double_spend_seen = meta.double_spend_seen;
tx_infos.push_back(txi);
return true;
- }, true);
+ }, true, include_sensitive_data);
+ txpool_tx_meta_t meta;
for (const key_images_container::value_type& kee : m_spent_key_images) {
const crypto::key_image& k_image = kee.first;
const std::unordered_set<crypto::hash>& kei_image_set = kee.second;
@@ -679,9 +690,26 @@ namespace cryptonote
ki.id_hash = epee::string_tools::pod_to_hex(k_image);
for (const crypto::hash& tx_id_hash : kei_image_set)
{
+ if (!include_sensitive_data)
+ {
+ try
+ {
+ meta = m_blockchain.get_txpool_tx_meta(tx_id_hash);
+ if (!meta.relayed)
+ // Do not include that transaction if in restricted mode and it's not relayed
+ continue;
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to get tx meta from txpool: " << e.what());
+ return false;
+ }
+ }
ki.txs_hashes.push_back(epee::string_tools::pod_to_hex(tx_id_hash));
}
- key_image_infos.push_back(ki);
+ // Only return key images for which we have at least one tx that we can show for them
+ if (!ki.txs_hashes.empty())
+ key_image_infos.push_back(ki);
}
return true;
}
@@ -712,9 +740,10 @@ namespace cryptonote
txi.relayed = meta.relayed;
txi.last_relayed_time = meta.last_relayed_time;
txi.do_not_relay = meta.do_not_relay;
+ txi.double_spend_seen = meta.double_spend_seen;
tx_infos.push_back(txi);
return true;
- }, true);
+ }, true, false);
for (const key_images_container::value_type& kee : m_spent_key_images) {
std::vector<crypto::hash> tx_hashes;
@@ -843,7 +872,10 @@ namespace cryptonote
}
//if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure
if(m_blockchain.have_tx_keyimges_as_spent(tx))
+ {
+ txd.double_spend_seen = true;
return false;
+ }
//transaction is ok.
return true;
@@ -871,6 +903,39 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
+ void tx_memory_pool::mark_double_spend(const transaction &tx)
+ {
+ CRITICAL_REGION_LOCAL(m_transactions_lock);
+ CRITICAL_REGION_LOCAL1(m_blockchain);
+ LockedTXN lock(m_blockchain);
+ for(size_t i = 0; i!= tx.vin.size(); i++)
+ {
+ CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, void());
+ const key_images_container::const_iterator it = m_spent_key_images.find(itk.k_image);
+ if (it != m_spent_key_images.end())
+ {
+ for (const crypto::hash &txid: it->second)
+ {
+ txpool_tx_meta_t meta = m_blockchain.get_txpool_tx_meta(txid);
+ if (!meta.double_spend_seen)
+ {
+ MDEBUG("Marking " << txid << " as double spending " << itk.k_image);
+ meta.double_spend_seen = true;
+ try
+ {
+ m_blockchain.update_txpool_tx(txid, meta);
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to update tx meta: " << e.what());
+ // continue, not fatal
+ }
+ }
+ }
+ }
+ }
+ }
+ //---------------------------------------------------------------------------------
std::string tx_memory_pool::print_pool(bool short_format) const
{
std::stringstream ss;
@@ -890,6 +955,7 @@ namespace cryptonote
ss << "blob_size: " << meta.blob_size << std::endl
<< "fee: " << print_money(meta.fee) << std::endl
<< "kept_by_block: " << (meta.kept_by_block ? 'T' : 'F') << std::endl
+ << "double_spend_seen: " << (meta.double_spend_seen ? 'T' : 'F') << std::endl
<< "max_used_block_height: " << meta.max_used_block_height << std::endl
<< "max_used_block_id: " << meta.max_used_block_id << std::endl
<< "last_failed_height: " << meta.last_failed_height << std::endl
@@ -1044,7 +1110,7 @@ namespace cryptonote
remove.insert(txid);
}
return true;
- });
+ }, false);
size_t n_removed = 0;
if (!remove.empty())
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index 3e4ccb338..d657c6554 100644
--- a/src/cryptonote_core/tx_pool.h
+++ b/src/cryptonote_core/tx_pool.h
@@ -137,10 +137,11 @@ namespace cryptonote
* @param fee the transaction fee
* @param relayed return-by-reference was transaction relayed to us by the network?
* @param do_not_relay return-by-reference is transaction not to be relayed to the network?
+ * @param double_spend_seen return-by-reference was a double spend seen for that transaction?
*
* @return true unless the transaction cannot be found in the pool
*/
- bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay);
+ bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen);
/**
* @brief checks if the pool has a transaction with the given hash
@@ -233,29 +234,37 @@ namespace cryptonote
* @brief get a list of all transactions in the pool
*
* @param txs return-by-reference the list of transactions
+ * @param include_unrelayed_txes include unrelayed txes in the result
+ *
*/
- void get_transactions(std::list<transaction>& txs) const;
+ void get_transactions(std::list<transaction>& txs, bool include_unrelayed_txes = true) const;
/**
* @brief get a list of all transaction hashes in the pool
*
* @param txs return-by-reference the list of transactions
+ * @param include_unrelayed_txes include unrelayed txes in the result
+ *
*/
- void get_transaction_hashes(std::vector<crypto::hash>& txs) const;
+ void get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes = true) const;
/**
* @brief get (size, fee, receive time) for all transaction in the pool
*
* @param txs return-by-reference that data
+ * @param include_unrelayed_txes include unrelayed txes in the result
+ *
*/
- void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog) const;
+ void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_unrelayed_txes = true) const;
/**
* @brief get a summary statistics of all transaction hashes in the pool
*
* @param stats return-by-reference the pool statistics
+ * @param include_unrelayed_txes include unrelayed txes in the result
+ *
*/
- void get_transaction_stats(struct txpool_stats& stats) const;
+ void get_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes = true) const;
/**
* @brief get information about all transactions and key images in the pool
@@ -264,10 +273,11 @@ namespace cryptonote
*
* @param tx_infos return-by-reference the transactions' information
* @param key_image_infos return-by-reference the spent key images' information
+ * @param include_sensitive_data include unrelayed txes and fields that are sensitive to the node privacy
*
* @return true
*/
- bool get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const;
+ bool get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_sensitive_data = true) const;
/**
* @brief get information about all transactions and key images in the pool
@@ -308,6 +318,7 @@ namespace cryptonote
* nonzero fee
* hasn't been relayed too recently
* isn't old enough that relaying it is considered harmful
+ * Note a transaction can be "relayable" even if do_not_relay is true
*
* @param txs return-by-reference the transactions and their hashes
*
@@ -327,7 +338,7 @@ namespace cryptonote
*
* @return the number of transactions in the pool
*/
- size_t get_transactions_count() const;
+ size_t get_transactions_count(bool include_unrelayed_txes = true) const;
/**
* @brief get a string containing human-readable pool information
@@ -391,6 +402,8 @@ namespace cryptonote
time_t last_relayed_time; //!< the last time the transaction was relayed to the network
bool relayed; //!< whether or not the transaction has been relayed to the network
bool do_not_relay; //!< to avoid relay this transaction to the network
+
+ bool double_spend_seen; //!< true iff another tx was seen double spending this one
};
private:
@@ -478,6 +491,11 @@ namespace cryptonote
*/
bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, transaction &tx) const;
+ /**
+ * @brief mark all transactions double spending the one passed
+ */
+ void mark_double_spend(const transaction &tx);
+
//TODO: confirm the below comments and investigate whether or not this
// is the desired behavior
//! map key images to transactions which spent them
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index d16e5d7e5..e41491954 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -52,6 +52,7 @@
#define BLOCK_QUEUE_SIZE_THRESHOLD (100*1024*1024) // MB
#define REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD (5 * 1000000) // microseconds
#define IDLE_PEER_KICK_TIME (600 * 1000000) // microseconds
+#define PASSIVE_PEER_KICK_TIME (60 * 1000000) // microseconds
namespace cryptonote
{
@@ -1184,13 +1185,15 @@ skip:
std::vector<boost::uuids::uuid> kick_connections;
m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool
{
- if (context.m_state == cryptonote_connection_context::state_synchronizing)
+ if (context.m_state == cryptonote_connection_context::state_synchronizing || context.m_state == cryptonote_connection_context::state_before_handshake)
{
+ const bool passive = context.m_state == cryptonote_connection_context::state_before_handshake;
const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
const boost::posix_time::time_duration dt = now - context.m_last_request_time;
- if (dt.total_microseconds() > IDLE_PEER_KICK_TIME)
+ const int64_t threshold = passive ? PASSIVE_PEER_KICK_TIME : IDLE_PEER_KICK_TIME;
+ if (dt.total_microseconds() > threshold)
{
- MINFO(context << " kicking idle peer");
+ MINFO(context << " kicking " << (passive ? "passive" : "idle") << " peer");
kick_connections.push_back(context.m_connection_id);
}
}
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index 2c11dcb31..83b04bff5 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -814,7 +814,7 @@ bool t_rpc_command_executor::print_transaction_pool_long() {
}
else
{
- if (!m_rpc_server->on_get_transaction_pool(req, res) || res.status != CORE_RPC_STATUS_OK)
+ if (!m_rpc_server->on_get_transaction_pool(req, res, false) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
@@ -840,6 +840,7 @@ bool t_rpc_command_executor::print_transaction_pool_long() {
<< "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl
<< "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
+ << "double_spend_seen: " << (tx_info.double_spend_seen ? 'T' : 'F') << std::endl
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
<< "last_failed_height: " << tx_info.last_failed_height << std::endl
@@ -898,7 +899,7 @@ bool t_rpc_command_executor::print_transaction_pool_short() {
}
else
{
- if (!m_rpc_server->on_get_transaction_pool(req, res) || res.status != CORE_RPC_STATUS_OK)
+ if (!m_rpc_server->on_get_transaction_pool(req, res, false) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
@@ -922,6 +923,7 @@ bool t_rpc_command_executor::print_transaction_pool_short() {
<< "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl
<< "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
+ << "double_spend_seen: " << (tx_info.double_spend_seen ? 'T' : 'F') << std::endl
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
<< "last_failed_height: " << tx_info.last_failed_height << std::endl
@@ -954,7 +956,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() {
else
{
memset(&res.pool_stats, 0, sizeof(res.pool_stats));
- if (!m_rpc_server->on_get_transaction_pool_stats(req, res) || res.status != CORE_RPC_STATUS_OK)
+ if (!m_rpc_server->on_get_transaction_pool_stats(req, res, false) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
@@ -984,7 +986,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() {
tools::msg_writer() << n_transactions << " tx(es), " << res.pool_stats.bytes_total << " bytes total (min " << res.pool_stats.bytes_min << ", max " << res.pool_stats.bytes_max << ", avg " << avg_bytes << ", median " << res.pool_stats.bytes_med << ")" << std::endl
<< "fees " << cryptonote::print_money(res.pool_stats.fee_total) << " (avg " << cryptonote::print_money(n_transactions ? res.pool_stats.fee_total / n_transactions : 0) << " per tx" << ", " << cryptonote::print_money(res.pool_stats.bytes_total ? res.pool_stats.fee_total / res.pool_stats.bytes_total : 0) << " per byte)" << std::endl
- << res.pool_stats.num_not_relayed << " not relayed, " << res.pool_stats.num_failing << " failing, " << res.pool_stats.num_10m << " older than 10 minutes (oldest " << (res.pool_stats.oldest == 0 ? "-" : get_human_time_ago(res.pool_stats.oldest, now)) << "), " << backlog_message;
+ << res.pool_stats.num_double_spends << " double spends, " << res.pool_stats.num_not_relayed << " not relayed, " << res.pool_stats.num_failing << " failing, " << res.pool_stats.num_10m << " older than 10 minutes (oldest " << (res.pool_stats.oldest == 0 ? "-" : get_human_time_ago(res.pool_stats.oldest, now)) << "), " << backlog_message;
if (n_transactions > 1 && res.pool_stats.histo.size())
{
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 6162d649b..ee3ff160a 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -560,7 +560,7 @@ namespace nodetool
//configure self
m_net_server.set_threads_prefix("P2P");
- m_net_server.get_config_object().m_pcommands_handler = this;
+ m_net_server.get_config_object().set_handler(this);
m_net_server.get_config_object().m_invoke_timeout = P2P_DEFAULT_INVOKE_TIMEOUT;
m_net_server.set_connection_filter(this);
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index b3ce30d0c..9094ec732 100755
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -478,15 +478,17 @@ namespace cryptonote
// try the pool for any missing txes
size_t found_in_pool = 0;
std::unordered_set<crypto::hash> pool_tx_hashes;
+ std::unordered_map<crypto::hash, bool> double_spend_seen;
if (!missed_txs.empty())
{
- std::list<transaction> pool_txs;
- bool r = m_core.get_pool_transactions(pool_txs);
+ std::vector<tx_info> pool_tx_info;
+ std::vector<spent_key_image_info> pool_key_image_info;
+ bool r = m_core.get_pool_transactions_and_spent_keys_info(pool_tx_info, pool_key_image_info);
if(r)
{
// sort to match original request
std::list<transaction> sorted_txs;
- std::list<cryptonote::transaction>::const_iterator i;
+ std::vector<tx_info>::const_iterator i;
for (const crypto::hash &h: vh)
{
if (std::find(missed_txs.begin(), missed_txs.end(), h) == missed_txs.end())
@@ -500,11 +502,26 @@ namespace cryptonote
sorted_txs.push_back(std::move(txs.front()));
txs.pop_front();
}
- else if ((i = std::find_if(pool_txs.begin(), pool_txs.end(), [h](cryptonote::transaction &tx) { return h == cryptonote::get_transaction_hash(tx); })) != pool_txs.end())
+ else if ((i = std::find_if(pool_tx_info.begin(), pool_tx_info.end(), [h](const tx_info &txi) { return epee::string_tools::pod_to_hex(h) == txi.id_hash; })) != pool_tx_info.end())
{
- sorted_txs.push_back(*i);
+ cryptonote::transaction tx;
+ if (!cryptonote::parse_and_validate_tx_from_blob(i->tx_blob, tx))
+ {
+ res.status = "Failed to parse and validate tx from blob";
+ return true;
+ }
+ sorted_txs.push_back(tx);
missed_txs.remove(h);
pool_tx_hashes.insert(h);
+ const std::string hash_string = epee::string_tools::pod_to_hex(h);
+ for (const auto &ti: pool_tx_info)
+ {
+ if (ti.id_hash == hash_string)
+ {
+ double_spend_seen.insert(std::make_pair(h, ti.double_spend_seen));
+ break;
+ }
+ }
++found_in_pool;
}
}
@@ -530,11 +547,21 @@ namespace cryptonote
if (e.in_pool)
{
e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max();
+ if (double_spend_seen.find(tx_hash) != double_spend_seen.end())
+ {
+ e.double_spend_seen = double_spend_seen[tx_hash];
+ }
+ else
+ {
+ MERROR("Failed to determine double spend status for " << tx_hash);
+ e.double_spend_seen = false;
+ }
}
else
{
e.block_height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash);
e.block_timestamp = m_core.get_blockchain_storage().get_db().get_block_timestamp(e.block_height);
+ e.double_spend_seen = false;
}
// fill up old style responses too, in case an old wallet asks
@@ -564,7 +591,7 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
- bool core_rpc_server::on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res)
+ bool core_rpc_server::on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res, bool request_has_rpc_origin)
{
CHECK_CORE_BUSY();
std::vector<crypto::key_image> key_images;
@@ -596,7 +623,7 @@ namespace cryptonote
// check the pool too
std::vector<cryptonote::tx_info> txs;
std::vector<cryptonote::spent_key_image_info> ki;
- r = m_core.get_pool_transactions_and_spent_keys_info(txs, ki);
+ r = m_core.get_pool_transactions_and_spent_keys_info(txs, ki, !request_has_rpc_origin || !m_restricted);
if(!r)
{
res.status = "Failed";
@@ -843,26 +870,26 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
- bool core_rpc_server::on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res)
+ bool core_rpc_server::on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res, bool request_has_rpc_origin)
{
CHECK_CORE_BUSY();
- m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images);
+ m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, !request_has_rpc_origin || !m_restricted);
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
- bool core_rpc_server::on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res)
+ bool core_rpc_server::on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res, bool request_has_rpc_origin)
{
CHECK_CORE_BUSY();
- m_core.get_pool_transaction_hashes(res.tx_hashes);
+ m_core.get_pool_transaction_hashes(res.tx_hashes, !request_has_rpc_origin || !m_restricted);
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
- bool core_rpc_server::on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res)
+ bool core_rpc_server::on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res, bool request_has_rpc_origin)
{
CHECK_CORE_BUSY();
- m_core.get_pool_transaction_stats(res.pool_stats);
+ m_core.get_pool_transaction_stats(res.pool_stats, !request_has_rpc_origin || !m_restricted);
res.status = CORE_RPC_STATUS_OK;
return true;
}
@@ -1825,7 +1852,7 @@ namespace cryptonote
const command_line::arg_descriptor<bool> core_rpc_server::arg_restricted_rpc = {
"restricted-rpc"
- , "Restrict RPC to view only commands"
+ , "Restrict RPC to view only commands and do not return privacy sensitive data in RPC calls"
, false
};
} // namespace cryptonote
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index 73a308a72..7f252258c 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -122,7 +122,7 @@ namespace cryptonote
MAP_JON_RPC_WE_IF("flush_txpool", on_flush_txpool, COMMAND_RPC_FLUSH_TRANSACTION_POOL, !m_restricted)
MAP_JON_RPC_WE("get_output_histogram", on_get_output_histogram, COMMAND_RPC_GET_OUTPUT_HISTOGRAM)
MAP_JON_RPC_WE("get_version", on_get_version, COMMAND_RPC_GET_VERSION)
- MAP_JON_RPC_WE("get_coinbase_tx_sum", on_get_coinbase_tx_sum, COMMAND_RPC_GET_COINBASE_TX_SUM)
+ MAP_JON_RPC_WE_IF("get_coinbase_tx_sum", on_get_coinbase_tx_sum, COMMAND_RPC_GET_COINBASE_TX_SUM, !m_restricted)
MAP_JON_RPC_WE("get_fee_estimate", on_get_per_kb_fee_estimate, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE)
MAP_JON_RPC_WE_IF("get_alternate_chains",on_get_alternate_chains, COMMAND_RPC_GET_ALTERNATE_CHAINS, !m_restricted)
MAP_JON_RPC_WE_IF("relay_tx", on_relay_tx, COMMAND_RPC_RELAY_TX, !m_restricted)
@@ -137,7 +137,7 @@ namespace cryptonote
bool on_get_blocks_by_height(const COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response& res);
bool on_get_hashes(const COMMAND_RPC_GET_HASHES_FAST::request& req, COMMAND_RPC_GET_HASHES_FAST::response& res);
bool on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res);
- bool on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res);
+ bool on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res, bool request_has_rpc_origin = true);
bool on_get_indexes(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response& res);
bool on_send_raw_tx(const COMMAND_RPC_SEND_RAW_TX::request& req, COMMAND_RPC_SEND_RAW_TX::response& res);
bool on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res);
@@ -153,9 +153,9 @@ namespace cryptonote
bool on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res);
bool on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res);
bool on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res);
- bool on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res);
- bool on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res);
- bool on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res);
+ bool on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res, bool request_has_rpc_origin = true);
+ bool on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res, bool request_has_rpc_origin = true);
+ bool on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res, bool request_has_rpc_origin = true);
bool on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res);
bool on_get_limit(const COMMAND_RPC_GET_LIMIT::request& req, COMMAND_RPC_GET_LIMIT::response& res);
bool on_set_limit(const COMMAND_RPC_SET_LIMIT::request& req, COMMAND_RPC_SET_LIMIT::response& res);
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index ee2a79eb4..57799bb81 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -566,6 +566,7 @@ namespace cryptonote
std::string as_hex;
std::string as_json;
bool in_pool;
+ bool double_spend_seen;
uint64_t block_height;
uint64_t block_timestamp;
std::vector<uint64_t> output_indices;
@@ -575,6 +576,7 @@ namespace cryptonote
KV_SERIALIZE(as_hex)
KV_SERIALIZE(as_json)
KV_SERIALIZE(in_pool)
+ KV_SERIALIZE(double_spend_seen)
KV_SERIALIZE(block_height)
KV_SERIALIZE(block_timestamp)
KV_SERIALIZE(output_indices)
@@ -1357,6 +1359,8 @@ namespace cryptonote
bool relayed;
uint64_t last_relayed_time;
bool do_not_relay;
+ bool double_spend_seen;
+ std::string tx_blob;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(id_hash)
@@ -1372,6 +1376,8 @@ namespace cryptonote
KV_SERIALIZE(relayed)
KV_SERIALIZE(last_relayed_time)
KV_SERIALIZE(do_not_relay)
+ KV_SERIALIZE(double_spend_seen)
+ KV_SERIALIZE(tx_blob)
END_KV_SERIALIZE_MAP()
};
@@ -1480,6 +1486,7 @@ namespace cryptonote
uint32_t num_not_relayed;
uint64_t histo_98pc;
std::vector<txpool_histo> histo;
+ uint32_t num_double_spends;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(bytes_total)
@@ -1494,6 +1501,7 @@ namespace cryptonote
KV_SERIALIZE(num_not_relayed)
KV_SERIALIZE(histo_98pc)
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(histo)
+ KV_SERIALIZE(num_double_spends)
END_KV_SERIALIZE_MAP()
};
diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h
index 00f1e0caa..581048eaf 100644
--- a/src/rpc/message_data_structs.h
+++ b/src/rpc/message_data_structs.h
@@ -95,6 +95,7 @@ namespace rpc
uint64_t last_relayed_time;
bool relayed;
bool do_not_relay;
+ bool double_spend_seen;
};
typedef std::unordered_map<crypto::key_image, std::vector<crypto::hash> > key_images_with_tx_hashes;
diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp
index a40821d19..6e6e51528 100644
--- a/src/serialization/json_object.cpp
+++ b/src/serialization/json_object.cpp
@@ -755,6 +755,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx
INSERT_INTO_JSON_OBJECT(val, doc, last_relayed_time, tx.last_relayed_time);
INSERT_INTO_JSON_OBJECT(val, doc, relayed, tx.relayed);
INSERT_INTO_JSON_OBJECT(val, doc, do_not_relay, tx.do_not_relay);
+ INSERT_INTO_JSON_OBJECT(val, doc, double_spend_seen, tx.double_spend_seen);
}
@@ -777,6 +778,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::tx_in_pool& tx)
GET_FROM_JSON_OBJECT(val, tx.last_relayed_time, last_relayed_time);
GET_FROM_JSON_OBJECT(val, tx.relayed, relayed);
GET_FROM_JSON_OBJECT(val, tx.do_not_relay, do_not_relay);
+ GET_FROM_JSON_OBJECT(val, tx.double_spend_seen, double_spend_seen);
}
void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val)
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index c936f481e..24e7d54dd 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -111,6 +111,7 @@ namespace
const auto arg_wallet_file = wallet_args::arg_wallet_file();
const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to <arg>"), ""};
const command_line::arg_descriptor<std::string> arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""};
+ const command_line::arg_descriptor<std::string> arg_generate_from_spend_key = {"generate-from-spend-key", sw::tr("Generate deterministic wallet from spend key"), ""};
const command_line::arg_descriptor<std::string> arg_generate_from_keys = {"generate-from-keys", sw::tr("Generate wallet from private keys"), ""};
const command_line::arg_descriptor<std::string> arg_generate_from_multisig_keys = {"generate-from-multisig-keys", sw::tr("Generate a master wallet from multisig wallet keys"), ""};
const auto arg_generate_from_json = wallet_args::arg_generate_from_json();
@@ -161,20 +162,50 @@ namespace
return tools::scoped_message_writer(console_color_red, true, sw::tr("Error: "), el::Level::Error);
}
- bool is_it_true(const std::string& s)
+ bool parse_bool(const std::string& s, bool& result)
{
if (s == "1" || command_line::is_yes(s))
+ {
+ result = true;
return true;
+ }
+ if (s == "0" || command_line::is_no(s))
+ {
+ result = false;
+ return true;
+ }
boost::algorithm::is_iequal ignore_case{};
- if (boost::algorithm::equals("true", s, ignore_case))
+ if (boost::algorithm::equals("true", s, ignore_case) || boost::algorithm::equals(simple_wallet::tr("true"), s, ignore_case))
+ {
+ result = true;
return true;
- if (boost::algorithm::equals(simple_wallet::tr("true"), s, ignore_case))
+ }
+ if (boost::algorithm::equals("false", s, ignore_case) || boost::algorithm::equals(simple_wallet::tr("false"), s, ignore_case))
+ {
+ result = false;
return true;
+ }
return false;
}
+ template <typename F>
+ bool parse_bool_and_use(const std::string& s, F func)
+ {
+ bool r;
+ if (parse_bool(s, r))
+ {
+ func(r);
+ return true;
+ }
+ else
+ {
+ fail_msg_writer() << tr("invalid argument: must be either 0/1, true/false, y/n, yes/no");
+ return false;
+ }
+ }
+
const struct
{
const char *name;
@@ -507,8 +538,10 @@ bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string>
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
- m_wallet->always_confirm_transfers(is_it_true(args[1]));
- m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ parse_bool_and_use(args[1], [&](bool r) {
+ m_wallet->always_confirm_transfers(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
}
return true;
}
@@ -518,8 +551,10 @@ bool simple_wallet::set_print_ring_members(const std::vector<std::string> &args/
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
- m_wallet->print_ring_members(is_it_true(args[1]));
- m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ parse_bool_and_use(args[1], [&](bool r) {
+ m_wallet->print_ring_members(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
}
return true;
}
@@ -535,8 +570,10 @@ bool simple_wallet::set_store_tx_info(const std::vector<std::string> &args/* = s
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
- m_wallet->store_tx_info(is_it_true(args[1]));
- m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ parse_bool_and_use(args[1], [&](bool r) {
+ m_wallet->store_tx_info(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
}
return true;
}
@@ -631,14 +668,15 @@ bool simple_wallet::set_auto_refresh(const std::vector<std::string> &args/* = st
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
- const bool auto_refresh = is_it_true(args[1]);
- m_wallet->auto_refresh(auto_refresh);
- m_idle_mutex.lock();
- m_auto_refresh_enabled.store(auto_refresh, std::memory_order_relaxed);
- m_idle_cond.notify_one();
- m_idle_mutex.unlock();
+ parse_bool_and_use(args[1], [&](bool auto_refresh) {
+ m_wallet->auto_refresh(auto_refresh);
+ m_idle_mutex.lock();
+ m_auto_refresh_enabled.store(auto_refresh, std::memory_order_relaxed);
+ m_idle_cond.notify_one();
+ m_idle_mutex.unlock();
- m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
}
return true;
}
@@ -665,8 +703,10 @@ bool simple_wallet::set_confirm_missing_payment_id(const std::vector<std::string
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
- m_wallet->confirm_missing_payment_id(is_it_true(args[1]));
- m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ parse_bool_and_use(args[1], [&](bool r) {
+ m_wallet->confirm_missing_payment_id(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
}
return true;
}
@@ -676,8 +716,10 @@ bool simple_wallet::set_ask_password(const std::vector<std::string> &args/* = st
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
- m_wallet->ask_password(is_it_true(args[1]));
- m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ parse_bool_and_use(args[1], [&](bool r) {
+ m_wallet->ask_password(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
}
return true;
}
@@ -753,8 +795,10 @@ bool simple_wallet::set_merge_destinations(const std::vector<std::string> &args/
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
- m_wallet->merge_destinations(is_it_true(args[1]));
- m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ parse_bool_and_use(args[1], [&](bool r) {
+ m_wallet->merge_destinations(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
}
return true;
}
@@ -764,8 +808,10 @@ bool simple_wallet::set_confirm_backlog(const std::vector<std::string> &args/* =
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
- m_wallet->confirm_backlog(is_it_true(args[1]));
- m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ parse_bool_and_use(args[1], [&](bool r) {
+ m_wallet->confirm_backlog(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ });
}
return true;
}
@@ -1077,12 +1123,12 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
if (!handle_command_line(vm))
return false;
- if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_keys.empty()) + (!m_generate_from_multisig_keys.empty()) + (!m_generate_from_json.empty()) > 1)
+ if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_spend_key.empty()) + (!m_generate_from_keys.empty()) + (!m_generate_from_multisig_keys.empty()) + (!m_generate_from_json.empty()) > 1)
{
- fail_msg_writer() << tr("can't specify more than one of --generate-new-wallet=\"wallet_name\", --wallet-file=\"wallet_name\", --generate-from-view-key=\"wallet_name\", --generate-from-keys=\"wallet_name\", --generate-from-multisig-keys=\"wallet_name\" and --generate-from-json=\"jsonfilename\"");
+ fail_msg_writer() << tr("can't specify more than one of --generate-new-wallet=\"wallet_name\", --wallet-file=\"wallet_name\", --generate-from-view-key=\"wallet_name\", --generate-from-spend-key=\"wallet_name\", --generate-from-keys=\"wallet_name\", --generate-from-multisig-keys=\"wallet_name\" and --generate-from-json=\"jsonfilename\"");
return false;
}
- else if (m_generate_new.empty() && m_wallet_file.empty() && m_generate_from_view_key.empty() && m_generate_from_keys.empty() && m_generate_from_multisig_keys.empty() && m_generate_from_json.empty())
+ else if (m_generate_new.empty() && m_wallet_file.empty() && m_generate_from_view_key.empty() && m_generate_from_spend_key.empty() && m_generate_from_keys.empty() && m_generate_from_multisig_keys.empty() && m_generate_from_json.empty())
{
if(!ask_wallet_create_if_needed()) return false;
}
@@ -1190,6 +1236,25 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
bool r = new_wallet(vm, info.address, boost::none, viewkey);
CHECK_AND_ASSERT_MES(r, false, tr("account creation failed"));
}
+ else if (!m_generate_from_spend_key.empty())
+ {
+ m_wallet_file = m_generate_from_spend_key;
+ // parse spend secret key
+ std::string spendkey_string = command_line::input_line("Secret spend key: ");
+ if (std::cin.eof())
+ return false;
+ if (spendkey_string.empty()) {
+ fail_msg_writer() << tr("No data supplied, cancelled");
+ return false;
+ }
+ if (!epee::string_tools::hex_to_pod(spendkey_string, m_recovery_key))
+ {
+ fail_msg_writer() << tr("failed to parse spend key secret key");
+ return false;
+ }
+ bool r = new_wallet(vm, m_recovery_key, true, false, "");
+ CHECK_AND_ASSERT_MES(r, false, tr("account creation failed"));
+ }
else if (!m_generate_from_keys.empty())
{
m_wallet_file = m_generate_from_keys;
@@ -1531,6 +1596,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_
m_wallet_file = command_line::get_arg(vm, arg_wallet_file);
m_generate_new = command_line::get_arg(vm, arg_generate_new_wallet);
m_generate_from_view_key = command_line::get_arg(vm, arg_generate_from_view_key);
+ m_generate_from_spend_key = command_line::get_arg(vm, arg_generate_from_spend_key);
m_generate_from_keys = command_line::get_arg(vm, arg_generate_from_keys);
m_generate_from_multisig_keys = command_line::get_arg(vm, arg_generate_from_multisig_keys);
m_generate_from_json = command_line::get_arg(vm, arg_generate_from_json);
@@ -1543,6 +1609,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_
m_restore_height = command_line::get_arg(vm, arg_restore_height);
m_do_not_relay = command_line::get_arg(vm, arg_do_not_relay);
m_restoring = !m_generate_from_view_key.empty() ||
+ !m_generate_from_spend_key.empty() ||
!m_generate_from_keys.empty() ||
!m_generate_from_multisig_keys.empty() ||
!m_generate_from_json.empty() ||
@@ -1904,8 +1971,16 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args)
bool ok = true;
size_t max_mining_threads_count = (std::max)(tools::get_max_concurrency(), static_cast<unsigned>(2));
size_t arg_size = args.size();
- if(arg_size >= 3) req.ignore_battery = is_it_true(args[2]);
- if(arg_size >= 2) req.do_background_mining = is_it_true(args[1]);
+ if(arg_size >= 3)
+ {
+ if (!parse_bool_and_use(args[2], [&](bool r) { req.ignore_battery = r; }))
+ return true;
+ }
+ if(arg_size >= 2)
+ {
+ if (!parse_bool_and_use(args[1], [&](bool r) { req.do_background_mining = r; }))
+ return true;
+ }
if(arg_size >= 1)
{
uint16_t num = 1;
@@ -1988,7 +2063,7 @@ void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid,
message_writer(console_color_green, false) << "\r" <<
tr("Height ") << height << ", " <<
tr("txid ") << txid << ", " <<
- print_money(amount) << tr(" XMR, ") <<
+ print_money(amount) <<
tr("idx ") << subaddr_index;
if (m_auto_refresh_refreshing)
m_cmd_binder.print_prompt();
@@ -3426,7 +3501,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_)
local_args.push_back(amount_str);
if (!payment_id_str.empty())
local_args.push_back(payment_id_str);
- message_writer() << tr("Donating ") << amount_str << " XMR to The Monero Project (donate.getmonero.org/44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A).";
+ message_writer() << tr("Donating ") << amount_str << " to The Monero Project (donate.getmonero.org/44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A).";
transfer_new(local_args);
return true;
}
@@ -4415,15 +4490,18 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
try
{
m_wallet->update_pool_state();
- std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
+ std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments;
m_wallet->get_unconfirmed_payments(payments, m_current_subaddress_account, subaddr_indices);
- for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
- const tools::wallet2::payment_details &pd = i->second;
+ for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
+ const tools::wallet2::payment_details &pd = i->second.m_pd;
std::string payment_id = string_tools::pod_to_hex(i->first);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
std::string note = m_wallet->get_tx_note(pd.m_tx_hash);
- message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %d %s %s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str();
+ std::string double_spend_note;
+ if (i->second.m_double_spend_seen)
+ double_spend_note = tr("[Double spend seen on the network: this transaction may or may not end up being mined] ");
+ message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %d %s %s%s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note % double_spend_note).str();
}
}
catch (const std::exception& e)
@@ -5439,10 +5517,10 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args)
try
{
m_wallet->update_pool_state();
- std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments;
+ std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments;
m_wallet->get_unconfirmed_payments(pool_payments);
- for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
- const tools::wallet2::payment_details &pd = i->second;
+ for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
+ const tools::wallet2::payment_details &pd = i->second.m_pd;
if (pd.m_tx_hash == txid)
{
std::string payment_id = string_tools::pod_to_hex(i->first);
@@ -5455,6 +5533,8 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args)
success_msg_writer() << "Payment ID: " << payment_id;
success_msg_writer() << "Address index: " << pd.m_subaddr_index.minor;
success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid);
+ if (i->second.m_double_spend_seen)
+ success_msg_writer() << tr("Double spend seen on the network: this transaction may or may not end up being mined");
return true;
}
}
@@ -5546,6 +5626,7 @@ int main(int argc, char* argv[])
command_line::add_arg(desc_params, arg_wallet_file);
command_line::add_arg(desc_params, arg_generate_new_wallet);
command_line::add_arg(desc_params, arg_generate_from_view_key);
+ command_line::add_arg(desc_params, arg_generate_from_spend_key);
command_line::add_arg(desc_params, arg_generate_from_keys);
command_line::add_arg(desc_params, arg_generate_from_multisig_keys);
command_line::add_arg(desc_params, arg_generate_from_json);
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 639cee642..8100fda55 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -281,6 +281,7 @@ namespace cryptonote
std::string m_wallet_file;
std::string m_generate_new;
std::string m_generate_from_view_key;
+ std::string m_generate_from_spend_key;
std::string m_generate_from_keys;
std::string m_generate_from_multisig_keys;
std::string m_generate_from_json;
diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp
index 59eca3dd7..8a8243047 100644
--- a/src/wallet/api/transaction_history.cpp
+++ b/src/wallet/api/transaction_history.cpp
@@ -217,10 +217,10 @@ void TransactionHistoryImpl::refresh()
// unconfirmed payments (tx pool)
- std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> upayments;
+ std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> upayments;
m_wallet->m_wallet->get_unconfirmed_payments(upayments);
- for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
- const tools::wallet2::payment_details &pd = i->second;
+ for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
+ const tools::wallet2::payment_details &pd = i->second.m_pd;
std::string payment_id = string_tools::pod_to_hex(i->first);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index b8d05e813..ae4af99f6 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -305,9 +305,9 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string());
// compatibility checks
- if (!field_seed_found && !field_viewkey_found)
+ if (!field_seed_found && !field_viewkey_found && !field_spendkey_found)
{
- tools::fail_msg_writer() << tools::wallet2::tr("At least one of Electrum-style word list and private view key must be specified");
+ tools::fail_msg_writer() << tools::wallet2::tr("At least one of Electrum-style word list and private view key and private spend key must be specified");
return false;
}
if (field_seed_found && (field_viewkey_found || field_spendkey_found))
@@ -368,6 +368,10 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
{
wallet->generate(field_filename, field_password, recovery_key, recover, false);
}
+ else if (field_viewkey.empty() && !field_spendkey.empty())
+ {
+ wallet->generate(field_filename, field_password, spendkey, recover, false);
+ }
else
{
cryptonote::account_public_address address;
@@ -390,6 +394,11 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
}
address.m_spend_public_key = info.address.m_spend_public_key;
}
+ else
+ {
+ tools::fail_msg_writer() << tools::wallet2::tr("Address must be specified in order to create watch-only wallet");
+ return false;
+ }
wallet->generate(field_filename, field_password, address, viewkey);
}
else
@@ -444,6 +453,21 @@ std::string strjoin(const std::vector<size_t> &V, const char *sep)
return ss.str();
}
+static void emplace_or_replace(std::unordered_multimap<crypto::hash, tools::wallet2::pool_payment_details> &container,
+ const crypto::hash &key, const tools::wallet2::pool_payment_details &pd)
+{
+ auto range = container.equal_range(key);
+ for (auto i = range.first; i != range.second; ++i)
+ {
+ if (i->second.m_pd.m_tx_hash == pd.m_pd.m_tx_hash)
+ {
+ i->second = pd;
+ return;
+ }
+ }
+ container.emplace(key, pd);
+}
+
} //namespace
namespace tools
@@ -793,7 +817,7 @@ void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote
++num_vouts_received;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool)
+void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen)
{
// In this function, tx (probably) only contains the base information
// (that is, the prunable stuff may or may not be included)
@@ -1163,7 +1187,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
payment.m_timestamp = ts;
payment.m_subaddr_index = i.first;
if (pool) {
- m_unconfirmed_payments.emplace(payment_id, payment);
+ emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, double_spend_seen});
if (0 != m_callback)
m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount, payment.m_subaddr_index);
}
@@ -1241,7 +1265,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height)
{
TIME_MEASURE_START(miner_tx_handle_time);
- process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false);
+ process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false, false);
TIME_MEASURE_FINISH(miner_tx_handle_time);
TIME_MEASURE_START(txs_handle_time);
@@ -1252,7 +1276,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
cryptonote::transaction tx;
bool r = parse_and_validate_tx_base_from_blob(txblob, tx);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob);
- process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false);
+ process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false, false);
++idx;
}
TIME_MEASURE_FINISH(txs_handle_time);
@@ -1520,10 +1544,10 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei
void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes)
{
// remove pool txes to us that aren't in the pool anymore
- std::unordered_multimap<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin();
+ std::unordered_multimap<crypto::hash, wallet2::pool_payment_details>::iterator uit = m_unconfirmed_payments.begin();
while (uit != m_unconfirmed_payments.end())
{
- const crypto::hash &txid = uit->second.m_tx_hash;
+ const crypto::hash &txid = uit->second.m_pd.m_tx_hash;
bool found = false;
for (const auto &it2: tx_hashes)
{
@@ -1626,23 +1650,27 @@ void wallet2::update_pool_state(bool refreshed)
MDEBUG("update_pool_state done second loop");
// gather txids of new pool txes to us
- std::vector<crypto::hash> txids;
+ std::vector<std::pair<crypto::hash, bool>> txids;
for (const auto &txid: res.tx_hashes)
{
- if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end())
- {
- LOG_PRINT_L2("Already seen " << txid << ", skipped");
- continue;
- }
bool txid_found_in_up = false;
for (const auto &up: m_unconfirmed_payments)
{
- if (up.second.m_tx_hash == txid)
+ if (up.second.m_pd.m_tx_hash == txid)
{
txid_found_in_up = true;
break;
}
}
+ if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end())
+ {
+ // if it's for us, we want to keep track of whether we saw a double spend, so don't bail out
+ if (!txid_found_in_up)
+ {
+ LOG_PRINT_L2("Already seen " << txid << ", and not for us, skipped");
+ continue;
+ }
+ }
if (!txid_found_in_up)
{
LOG_PRINT_L1("Found new pool tx: " << txid);
@@ -1670,7 +1698,7 @@ void wallet2::update_pool_state(bool refreshed)
if (!found)
{
// not one of those we sent ourselves
- txids.push_back(txid);
+ txids.push_back({txid, false});
}
else
{
@@ -1680,6 +1708,7 @@ void wallet2::update_pool_state(bool refreshed)
else
{
LOG_PRINT_L1("Already saw that one, it's for us");
+ txids.push_back({txid, true});
}
}
@@ -1688,8 +1717,8 @@ void wallet2::update_pool_state(bool refreshed)
{
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res;
- for (const auto &txid: txids)
- req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
+ for (const auto &p: txids)
+ req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first));
MDEBUG("asking for " << txids.size() << " transactions");
req.decode_as_json = false;
m_daemon_rpc_mutex.lock();
@@ -1711,10 +1740,11 @@ void wallet2::update_pool_state(bool refreshed)
{
if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash))
{
- const std::vector<crypto::hash>::const_iterator i = std::find(txids.begin(), txids.end(), tx_hash);
+ const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(),
+ [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; });
if (i != txids.end())
{
- process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true);
+ process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true, tx_entry.double_spend_seen);
m_scanned_pool_txs[0].insert(tx_hash);
if (m_scanned_pool_txs[0].size() > 5000)
{
@@ -3073,11 +3103,11 @@ void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wall
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
+void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) {
- if ((!subaddr_account || *subaddr_account == i->second.m_subaddr_index.major) &&
- (subaddr_indices.empty() || subaddr_indices.count(i->second.m_subaddr_index.minor) == 1))
+ if ((!subaddr_account || *subaddr_account == i->second.m_pd.m_subaddr_index.major) &&
+ (subaddr_indices.empty() || subaddr_indices.count(i->second.m_pd.m_subaddr_index.minor) == 1))
unconfirmed_payments.push_back(*i);
}
}
@@ -5130,7 +5160,7 @@ void wallet2::light_wallet_get_address_txs()
payments_txs.push_back(p.second.m_tx_hash);
std::vector<crypto::hash> unconfirmed_payments_txs;
for(const auto &up: m_unconfirmed_payments)
- unconfirmed_payments_txs.push_back(up.second.m_tx_hash);
+ unconfirmed_payments_txs.push_back(up.second.m_pd.m_tx_hash);
// for balance calculation
uint64_t wallet_total_sent = 0;
@@ -5196,7 +5226,11 @@ void wallet2::light_wallet_get_address_txs()
if (t.mempool) {
if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) {
pool_txs.push_back(tx_hash);
- m_unconfirmed_payments.emplace(tx_hash, payment);
+ // assume false as we don't get that info from the light wallet server
+ crypto::hash payment_id;
+ THROW_WALLET_EXCEPTION_IF(!epee::string_tools::hex_to_pod(t.payment_id, payment_id),
+ error::wallet_internal_error, "Failed to parse payment id");
+ emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, false});
if (0 != m_callback) {
m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount);
}
@@ -5326,11 +5360,27 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image,
crypto::key_image calculated_key_image;
cryptonote::keypair in_ephemeral;
- // Subaddresses aren't supported in mymonero/openmonero yet. Using empty values.
- const std::vector<crypto::public_key> additional_tx_pub_keys;
- const crypto::public_key pkey = crypto::null_pkey;
-
- cryptonote::generate_key_image_helper(get_account().get_keys(), m_subaddresses, pkey, tx_public_key, additional_tx_pub_keys, out_index, in_ephemeral, calculated_key_image);
+ // Subaddresses aren't supported in mymonero/openmonero yet. Roll out the original scheme:
+ // compute D = a*R
+ // compute P = Hs(D || i)*G + B
+ // compute x = Hs(D || i) + b (and check if P==x*G)
+ // compute I = x*Hp(P)
+ const account_keys& ack = get_account().get_keys();
+ crypto::key_derivation derivation;
+ bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, derivation);
+ CHECK_AND_ASSERT_MES(r, false, "failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")");
+
+ r = crypto::derive_public_key(derivation, out_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub);
+ CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key (" << derivation << ", " << out_index << ", " << ack.m_account_address.m_spend_public_key << ")");
+
+ crypto::derive_secret_key(derivation, out_index, ack.m_spend_secret_key, in_ephemeral.sec);
+ crypto::public_key out_pkey_test;
+ r = crypto::secret_key_to_public_key(in_ephemeral.sec, out_pkey_test);
+ CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_key(" << in_ephemeral.sec << ")");
+ CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_pkey_test, false, "derived secret key doesn't match derived public key");
+
+ crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, calculated_key_image);
+
index_keyimage_map.emplace(out_index, calculated_key_image);
m_key_image_cache.emplace(tx_public_key, index_keyimage_map);
return key_image == calculated_key_image;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 7b3cb8437..3603faaca 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -244,6 +244,12 @@ namespace tools
bool m_incoming;
};
+ struct pool_payment_details
+ {
+ payment_details m_pd;
+ bool m_double_spend_seen;
+ };
+
struct unconfirmed_transfer_details
{
cryptonote::transaction_prefix m_tx;
@@ -530,7 +536,7 @@ namespace tools
void get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments,
uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
- void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
+ void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
uint64_t get_blockchain_current_height() const { return m_local_bc_height; }
void rescan_spent();
@@ -585,7 +591,7 @@ namespace tools
std::unordered_map<crypto::hash, payment_details> m;
a & m;
for (std::unordered_map<crypto::hash, payment_details>::const_iterator i = m.begin(); i != m.end(); ++i)
- m_unconfirmed_payments.insert(*i);
+ m_unconfirmed_payments.insert(std::make_pair(i->first, pool_payment_details{i->second, false}));
}
if(ver < 14)
return;
@@ -607,7 +613,15 @@ namespace tools
a & m_address_book;
if(ver < 17)
return;
- a & m_unconfirmed_payments;
+ if (ver < 21)
+ {
+ // we're loading an old version, where m_unconfirmed_payments payload was payment_details
+ std::unordered_map<crypto::hash, payment_details> m;
+ a & m;
+ for (const auto &i: m)
+ m_unconfirmed_payments.insert(std::make_pair(i.first, pool_payment_details{i.second, false}));
+ return;
+ }
if(ver < 18)
return;
a & m_scanned_pool_txs[0];
@@ -621,6 +635,9 @@ namespace tools
if(ver < 21)
return;
a & m_attributes;
+ if(ver < 22)
+ return;
+ a & m_unconfirmed_payments;
}
/*!
@@ -797,7 +814,7 @@ namespace tools
* \param password Password of wallet file
*/
bool load_keys(const std::string& keys_file_name, const std::string& password);
- void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool);
+ void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen);
void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices);
void detach_blockchain(uint64_t height);
void get_short_chain_history(std::list<crypto::hash>& ids) const;
@@ -846,7 +863,7 @@ namespace tools
std::atomic<uint64_t> m_local_bc_height; //temporary workaround
std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs;
std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs;
- std::unordered_multimap<crypto::hash, payment_details> m_unconfirmed_payments;
+ std::unordered_multimap<crypto::hash, pool_payment_details> m_unconfirmed_payments;
std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys;
cryptonote::checkpoints m_checkpoints;
std::unordered_map<crypto::hash, std::vector<crypto::secret_key>> m_additional_tx_keys;
@@ -908,9 +925,10 @@ namespace tools
std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> > m_key_image_cache;
};
}
-BOOST_CLASS_VERSION(tools::wallet2, 21)
+BOOST_CLASS_VERSION(tools::wallet2, 22)
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8)
BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2)
+BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1)
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7)
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 5)
BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
@@ -1137,7 +1155,14 @@ namespace boost
}
a & x.m_subaddr_index;
}
-
+
+ template <class Archive>
+ inline void serialize(Archive& a, tools::wallet2::pool_payment_details& x, const boost::serialization::version_type ver)
+ {
+ a & x.m_pd;
+ a & x.m_double_spend_seen;
+ }
+
template <class Archive>
inline void serialize(Archive& a, tools::wallet2::address_book_row& x, const boost::serialization::version_type ver)
{
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 9e6a97bdc..5dbf30419 100755
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -299,8 +299,9 @@ namespace tools
entry.subaddr_index = { pd.m_subaddr_account, 0 };
}
//------------------------------------------------------------------------------------------------------------------------------
- void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd)
+ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &ppd)
{
+ const tools::wallet2::payment_details &pd = ppd.m_pd;
entry.txid = string_tools::pod_to_hex(pd.m_tx_hash);
entry.payment_id = string_tools::pod_to_hex(payment_id);
if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
@@ -311,6 +312,7 @@ namespace tools
entry.unlock_time = pd.m_unlock_time;
entry.fee = 0; // TODO
entry.note = m_wallet->get_tx_note(pd.m_tx_hash);
+ entry.double_spend_seen = ppd.m_double_spend_seen;
entry.type = "pool";
entry.subaddr_index = pd.m_subaddr_index;
}
@@ -1357,9 +1359,9 @@ namespace tools
{
m_wallet->update_pool_state();
- std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
+ std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments;
m_wallet->get_unconfirmed_payments(payments, req.account_index, req.subaddr_indices);
- for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
+ for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
res.pool.push_back(wallet_rpc::transfer_entry());
fill_transfer_entry(res.pool.back(), i->first, i->second);
}
@@ -1430,10 +1432,10 @@ namespace tools
m_wallet->update_pool_state();
- std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments;
+ std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments;
m_wallet->get_unconfirmed_payments(pool_payments);
- for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
- if (i->second.m_tx_hash == txid)
+ for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
+ if (i->second.m_pd.m_tx_hash == txid)
{
fill_transfer_entry(res.transfer, i->first, i->second);
return true;
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index b38726cb7..a2677ef1b 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -163,7 +163,7 @@ namespace tools
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd);
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd);
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd);
- void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd);
+ void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &pd);
bool not_open(epee::json_rpc::error& er);
uint64_t adjust_mixin(uint64_t mixin);
void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code);
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index ffc2e2d49..06f2456c3 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -794,6 +794,7 @@ namespace wallet_rpc
std::string type;
uint64_t unlock_time;
cryptonote::subaddress_index subaddr_index;
+ bool double_spend_seen;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(txid);
@@ -807,6 +808,7 @@ namespace wallet_rpc
KV_SERIALIZE(type);
KV_SERIALIZE(unlock_time)
KV_SERIALIZE(subaddr_index);
+ KV_SERIALIZE(double_spend_seen)
END_KV_SERIALIZE_MAP()
};
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a5f5335db..762eee776 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -111,7 +111,7 @@ add_test(
COMMAND hash-target-tests)
set(enabled_tests
- coretests
+ core_tests
difficulty
hash
performance_tests
diff --git a/tests/core_proxy/core_proxy.cpp b/tests/core_proxy/core_proxy.cpp
index a0be3db96..0dc314b49 100644
--- a/tests/core_proxy/core_proxy.cpp
+++ b/tests/core_proxy/core_proxy.cpp
@@ -71,7 +71,7 @@ int main(int argc, char* argv[])
TRY_ENTRY();
-
+ tools::on_startup();
string_tools::set_module_name_and_folder(argv[0]);
//set up logging options
diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt
index a24bd4fce..68f2e9816 100644
--- a/tests/core_tests/CMakeLists.txt
+++ b/tests/core_tests/CMakeLists.txt
@@ -58,10 +58,10 @@ set(core_tests_headers
v2_tests.h
rct.h)
-add_executable(coretests
+add_executable(core_tests
${core_tests_sources}
${core_tests_headers})
-target_link_libraries(coretests
+target_link_libraries(core_tests
PRIVATE
cryptonote_core
p2p
@@ -69,10 +69,10 @@ target_link_libraries(coretests
epee
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})
-set_property(TARGET coretests
+set_property(TARGET core_tests
PROPERTY
FOLDER "tests")
add_test(
- NAME coretests
- COMMAND coretests --generate_and_play_test_data)
+ NAME core_tests
+ COMMAND core_tests --generate_and_play_test_data)
diff --git a/tests/core_tests/block_reward.cpp b/tests/core_tests/block_reward.cpp
index 9f3939652..8e68554d3 100644
--- a/tests/core_tests/block_reward.cpp
+++ b/tests/core_tests/block_reward.cpp
@@ -29,8 +29,6 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "chaingen.h"
-#include "chaingen_tests_list.h"
-
#include "block_reward.h"
using namespace epee;
diff --git a/tests/core_tests/block_validation.cpp b/tests/core_tests/block_validation.cpp
index db44cd279..94b636f82 100644
--- a/tests/core_tests/block_validation.cpp
+++ b/tests/core_tests/block_validation.cpp
@@ -29,7 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "chaingen.h"
-#include "chaingen_tests_list.h"
+#include "block_validation.h"
using namespace epee;
using namespace cryptonote;
diff --git a/tests/core_tests/chain_split_1.cpp b/tests/core_tests/chain_split_1.cpp
index eaaa3e045..79aecb1c0 100644
--- a/tests/core_tests/chain_split_1.cpp
+++ b/tests/core_tests/chain_split_1.cpp
@@ -29,7 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "chaingen.h"
-#include "chaingen_tests_list.h"
+#include "chain_split_1.h"
using namespace std;
diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp
index b04d05219..01cf00f7a 100644
--- a/tests/core_tests/chain_switch_1.cpp
+++ b/tests/core_tests/chain_switch_1.cpp
@@ -29,7 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "chaingen.h"
-#include "chaingen_tests_list.h"
+#include "chain_switch_1.h"
using namespace epee;
using namespace cryptonote;
diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp
index 3e5b949c8..9eba347cd 100644
--- a/tests/core_tests/chaingen_main.cpp
+++ b/tests/core_tests/chaingen_main.cpp
@@ -47,6 +47,7 @@ namespace
int main(int argc, char* argv[])
{
TRY_ENTRY();
+ tools::on_startup();
epee::string_tools::set_module_name_and_folder(argv[0]);
//set up logging options
diff --git a/tests/core_tests/double_spend.cpp b/tests/core_tests/double_spend.cpp
index 58114b026..d82120254 100644
--- a/tests/core_tests/double_spend.cpp
+++ b/tests/core_tests/double_spend.cpp
@@ -29,7 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "chaingen.h"
-#include "chaingen_tests_list.h"
+#include "double_spend.h"
using namespace epee;
using namespace cryptonote;
diff --git a/tests/core_tests/integer_overflow.cpp b/tests/core_tests/integer_overflow.cpp
index 5a9604fc1..4abdfbff5 100644
--- a/tests/core_tests/integer_overflow.cpp
+++ b/tests/core_tests/integer_overflow.cpp
@@ -29,8 +29,6 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "chaingen.h"
-#include "chaingen_tests_list.h"
-
#include "integer_overflow.h"
using namespace epee;
diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp
index 8a38cbc22..50f65cc67 100644
--- a/tests/core_tests/rct.cpp
+++ b/tests/core_tests/rct.cpp
@@ -30,7 +30,7 @@
#include "ringct/rctSigs.h"
#include "chaingen.h"
-#include "chaingen_tests_list.h"
+#include "rct.h"
using namespace epee;
using namespace crypto;
diff --git a/tests/core_tests/ring_signature_1.cpp b/tests/core_tests/ring_signature_1.cpp
index f9ec68e45..43c63dc53 100644
--- a/tests/core_tests/ring_signature_1.cpp
+++ b/tests/core_tests/ring_signature_1.cpp
@@ -29,7 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "chaingen.h"
-#include "chaingen_tests_list.h"
+#include "ring_signature_1.h"
using namespace epee;
using namespace cryptonote;
diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp
index 0e4b2e71a..9987f80d6 100644
--- a/tests/core_tests/tx_validation.cpp
+++ b/tests/core_tests/tx_validation.cpp
@@ -29,7 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "chaingen.h"
-#include "chaingen_tests_list.h"
+#include "tx_validation.h"
using namespace epee;
using namespace crypto;
diff --git a/tests/core_tests/v2_tests.cpp b/tests/core_tests/v2_tests.cpp
index 6c94ac76c..6c2f91fcf 100644
--- a/tests/core_tests/v2_tests.cpp
+++ b/tests/core_tests/v2_tests.cpp
@@ -29,7 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "chaingen.h"
-#include "chaingen_tests_list.h"
+#include "v2_tests.h"
using namespace epee;
using namespace crypto;
diff --git a/tests/functional_tests/main.cpp b/tests/functional_tests/main.cpp
index 4c9e073d4..35a0bb9bd 100644
--- a/tests/functional_tests/main.cpp
+++ b/tests/functional_tests/main.cpp
@@ -34,6 +34,7 @@
using namespace epee;
#include "common/command_line.h"
+#include "common/util.h"
#include "transactions_flow_test.h"
namespace po = boost::program_options;
@@ -58,6 +59,7 @@ namespace
int main(int argc, char* argv[])
{
TRY_ENTRY();
+ tools::on_startup();
string_tools::set_module_name_and_folder(argv[0]);
//set up logging options
diff --git a/tests/fuzz/fuzzer.cpp b/tests/fuzz/fuzzer.cpp
index 3edf8cd19..756a8c847 100644
--- a/tests/fuzz/fuzzer.cpp
+++ b/tests/fuzz/fuzzer.cpp
@@ -29,6 +29,7 @@
#include <boost/program_options.hpp>
#include "include_base_utils.h"
#include "common/command_line.h"
+#include "common/util.h"
#include "fuzzer.h"
#if (!defined(__clang__) || (__clang__ < 5))
@@ -48,6 +49,7 @@ using namespace boost::program_options;
int run_fuzzer(int argc, const char **argv, Fuzzer &fuzzer)
{
TRY_ENTRY();
+ tools::on_startup();
string_tools::set_module_name_and_folder(argv[0]);
//set up logging options
diff --git a/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp
index 853ad7c8d..3434cd530 100644
--- a/tests/libwallet_api_tests/main.cpp
+++ b/tests/libwallet_api_tests/main.cpp
@@ -33,6 +33,7 @@
#include "wallet/wallet2_api.h"
#include "wallet/wallet2.h"
#include "include_base_utils.h"
+#include "common/util.h"
#include <boost/chrono/chrono.hpp>
#include <boost/filesystem.hpp>
@@ -1138,6 +1139,7 @@ TEST_F(WalletManagerMainnetTest, RecoverAndRefreshWalletMainNetAsync)
int main(int argc, char** argv)
{
+ tools::on_startup();
// we can override default values for "TESTNET_DAEMON_ADDRESS" and "WALLETS_ROOT_DIR"
const char * testnet_daemon_addr = std::getenv("TESTNET_DAEMON_ADDRESS");
diff --git a/tests/net_load_tests/clt.cpp b/tests/net_load_tests/clt.cpp
index 376d7ee53..a5e5b7c0c 100644
--- a/tests/net_load_tests/clt.cpp
+++ b/tests/net_load_tests/clt.cpp
@@ -193,7 +193,7 @@ namespace
{
m_thread_count = (std::max)(min_thread_count, boost::thread::hardware_concurrency() / 2);
- m_tcp_server.get_config_object().m_pcommands_handler = &m_commands_handler;
+ m_tcp_server.get_config_object().set_handler(&m_commands_handler);
m_tcp_server.get_config_object().m_invoke_timeout = CONNECTION_TIMEOUT;
ASSERT_TRUE(m_tcp_server.init_server(clt_port, "127.0.0.1"));
@@ -238,9 +238,10 @@ namespace
static void TearDownTestCase()
{
// Stop server
- test_levin_commands_handler commands_handler;
- test_tcp_server tcp_server(epee::net_utils::e_connection_type_NET);
- tcp_server.get_config_object().m_pcommands_handler = &commands_handler;
+ test_levin_commands_handler *commands_handler_ptr = new test_levin_commands_handler();
+ test_levin_commands_handler &commands_handler = *commands_handler_ptr;
+ test_tcp_server tcp_server(epee::net_utils::e_connection_type_RPC);
+ tcp_server.get_config_object().set_handler(commands_handler_ptr, [](epee::levin::levin_commands_handler<test_connection_context> *handler)->void { delete handler; });
tcp_server.get_config_object().m_invoke_timeout = CONNECTION_TIMEOUT;
if (!tcp_server.init_server(clt_port, "127.0.0.1")) return;
@@ -627,6 +628,7 @@ TEST_F(net_load_test_clt, permament_open_and_close_and_connections_closed_by_ser
int main(int argc, char** argv)
{
+ tools::on_startup();
epee::debug::get_set_enable_assert(true, false);
//set up logging options
mlog_configure(mlog_get_default_log_path("net_load_tests_clt.log"), true);
diff --git a/tests/net_load_tests/net_load_tests.h b/tests/net_load_tests/net_load_tests.h
index f74282683..ce9d8b6fe 100644
--- a/tests/net_load_tests/net_load_tests.h
+++ b/tests/net_load_tests/net_load_tests.h
@@ -151,6 +151,11 @@ namespace net_load_tests
bool handle_new_connection(const boost::uuids::uuid& connection_id, bool ignore_close_fails = false)
{
size_t idx = m_next_opened_conn_idx.fetch_add(1, std::memory_order_relaxed);
+ if (idx >= m_connections.size())
+ {
+ LOG_PRINT_L0("ERROR: connections overflow");
+ exit(1);
+ }
m_connections[idx] = connection_id;
size_t prev_connection_count = m_opened_connection_count.fetch_add(1, std::memory_order_relaxed);
diff --git a/tests/net_load_tests/srv.cpp b/tests/net_load_tests/srv.cpp
index e6dee1639..a987aa4e2 100644
--- a/tests/net_load_tests/srv.cpp
+++ b/tests/net_load_tests/srv.cpp
@@ -215,6 +215,7 @@ namespace
int main(int argc, char** argv)
{
+ tools::on_startup();
//set up logging options
mlog_configure(mlog_get_default_log_path("net_load_tests_srv.log"), true);
@@ -224,8 +225,8 @@ int main(int argc, char** argv)
if (!tcp_server.init_server(srv_port, "127.0.0.1"))
return 1;
- srv_levin_commands_handler commands_handler(tcp_server);
- tcp_server.get_config_object().m_pcommands_handler = &commands_handler;
+ srv_levin_commands_handler *commands_handler = new srv_levin_commands_handler(tcp_server);
+ tcp_server.get_config_object().set_handler(commands_handler, [](epee::levin::levin_commands_handler<test_connection_context> *handler) { delete handler; });
tcp_server.get_config_object().m_invoke_timeout = 10000;
//tcp_server.get_config_object().m_max_packet_size = max_packet_size;
diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp
index 3c0283eca..459eecba4 100644
--- a/tests/performance_tests/main.cpp
+++ b/tests/performance_tests/main.cpp
@@ -28,6 +28,7 @@
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+#include "common/util.h"
#include "performance_tests.h"
#include "performance_utils.h"
@@ -48,6 +49,7 @@
int main(int argc, char** argv)
{
+ tools::on_startup();
set_process_affinity(1);
set_thread_high_priority();
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt
index c7efcf074..e10648d20 100644
--- a/tests/unit_tests/CMakeLists.txt
+++ b/tests/unit_tests/CMakeLists.txt
@@ -55,6 +55,7 @@ set(unit_tests_sources
serialization.cpp
sha256.cpp
slow_memmem.cpp
+ subaddress.cpp
test_tx_utils.cpp
test_peerlist.cpp
test_protocol_pack.cpp
diff --git a/tests/unit_tests/epee_levin_protocol_handler_async.cpp b/tests/unit_tests/epee_levin_protocol_handler_async.cpp
index d2aa31555..c749c2531 100644
--- a/tests/unit_tests/epee_levin_protocol_handler_async.cpp
+++ b/tests/unit_tests/epee_levin_protocol_handler_async.cpp
@@ -187,9 +187,11 @@ namespace
typedef std::unique_ptr<test_connection> test_connection_ptr;
- async_protocol_handler_test()
+ async_protocol_handler_test():
+ m_pcommands_handler(new test_levin_commands_handler()),
+ m_commands_handler(*m_pcommands_handler)
{
- m_handler_config.m_pcommands_handler = &m_commands_handler;
+ m_handler_config.set_handler(m_pcommands_handler, [](epee::levin::levin_commands_handler<test_levin_connection_context> *handler) { delete handler; });
m_handler_config.m_invoke_timeout = invoke_timeout;
m_handler_config.m_max_packet_size = max_packet_size;
}
@@ -212,7 +214,7 @@ namespace
protected:
boost::asio::io_service m_io_service;
test_levin_protocol_handler_config m_handler_config;
- test_levin_commands_handler m_commands_handler;
+ test_levin_commands_handler *m_pcommands_handler, &m_commands_handler;
};
class positive_test_connection_to_levin_protocol_handler_calls : public async_protocol_handler_test
diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp
index 2b0904224..c235f49fd 100644
--- a/tests/unit_tests/hardfork.cpp
+++ b/tests/unit_tests/hardfork.cpp
@@ -115,13 +115,13 @@ public:
virtual void add_txpool_tx(const transaction &tx, const txpool_tx_meta_t& details) {}
virtual void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t& details) {}
- virtual uint64_t get_txpool_tx_count() const { return 0; }
+ virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const { return 0; }
virtual bool txpool_has_tx(const crypto::hash &txid) const { return false; }
virtual void remove_txpool_tx(const crypto::hash& txid) {}
virtual txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const { return txpool_tx_meta_t(); }
virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const { return false; }
virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const { return ""; }
- virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false) const { return false; }
+ virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = false) const { return false; }
virtual void add_block( const block& blk
, const size_t& block_size
diff --git a/tests/unit_tests/main.cpp b/tests/unit_tests/main.cpp
index 1706c43c9..a6d398883 100644
--- a/tests/unit_tests/main.cpp
+++ b/tests/unit_tests/main.cpp
@@ -35,6 +35,7 @@
#include "include_base_utils.h"
#include "common/command_line.h"
+#include "common/util.h"
#include "unit_tests_utils.h"
namespace po = boost::program_options;
@@ -43,6 +44,7 @@ boost::filesystem::path unit_test::data_dir;
int main(int argc, char** argv)
{
+ tools::on_startup();
epee::string_tools::set_module_name_and_folder(argv[0]);
mlog_configure(mlog_get_default_log_path("unit_tests.log"), true);
epee::debug::get_set_enable_assert(true, false);
diff --git a/tests/unit_tests/subaddress.cpp b/tests/unit_tests/subaddress.cpp
new file mode 100644
index 000000000..c304b7347
--- /dev/null
+++ b/tests/unit_tests/subaddress.cpp
@@ -0,0 +1,118 @@
+// Copyright (c) 2014-2017, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+#include <boost/filesystem.hpp>
+#include "gtest/gtest.h"
+
+#include "include_base_utils.h"
+#include "wallet/wallet2.h"
+#include "crypto/crypto.h"
+#include "cryptonote_basic/account.h"
+#include "cryptonote_basic/cryptonote_basic_impl.h"
+#include "wallet/api/subaddress.h"
+
+class WalletSubaddress : public ::testing::Test
+{
+ protected:
+ virtual void SetUp()
+ {
+ try
+ {
+ w1.generate(wallet_name, password, recovery_key, true, false);
+ }
+ catch (const std::exception& e)
+ {
+ LOG_ERROR("failed to generate wallet: " << e.what());
+ throw e;
+ }
+
+ w1.add_subaddress_account(test_label);
+ w1.set_subaddress_label(subaddress_index, test_label);
+ }
+
+ virtual void TearDown()
+ {
+ boost::filesystem::wpath wallet_file(wallet_name);
+ boost::filesystem::wpath wallet_address_file(wallet_name + ".address.txt");
+ boost::filesystem::wpath wallet_keys_file(wallet_name + ".keys");
+
+ if ( boost::filesystem::exists(wallet_file) )
+ boost::filesystem::remove(wallet_file);
+
+ if ( boost::filesystem::exists(wallet_address_file) )
+ boost::filesystem::remove(wallet_address_file);
+
+ if ( boost::filesystem::exists(wallet_keys_file) )
+ boost::filesystem::remove(wallet_keys_file);
+ }
+
+ tools::wallet2 w1;
+ std::string path_working_dir = ".";
+ std::string path_test_wallet = "test_wallet";
+ const std::string wallet_name = path_working_dir + "/" + path_test_wallet;
+ const std::string password = "testpass";
+ crypto::secret_key recovery_key = crypto::secret_key();
+ const std::string test_label = "subaddress test label";
+
+ uint32_t major_index = 0;
+ uint32_t minor_index = 0;
+ const cryptonote::subaddress_index subaddress_index = {major_index, minor_index};
+};
+
+TEST_F(WalletSubaddress, GetSubaddressLabel)
+{
+ EXPECT_EQ(test_label, w1.get_subaddress_label(subaddress_index));
+}
+
+TEST_F(WalletSubaddress, AddSubaddress)
+{
+ std::string label = "test adding subaddress";
+ w1.add_subaddress(0, label);
+ EXPECT_EQ(label, w1.get_subaddress_label({0, 1}));
+}
+
+TEST_F(WalletSubaddress, OutOfBoundsIndexes)
+{
+ try
+ {
+ w1.get_subaddress_label({1,0});
+ }
+ catch(const std::exception& e)
+ {
+ EXPECT_STREQ("index_major is out of bound", e.what());
+ }
+ try
+ {
+ w1.get_subaddress_label({0,2});
+ }
+ catch(const std::exception& e)
+ {
+ EXPECT_STREQ("index.minor is out of bound", e.what());
+ }
+}
diff --git a/utils/gpg_keys/stoffu.asc b/utils/gpg_keys/stoffu.asc
new file mode 100644
index 000000000..5067ccf90
--- /dev/null
+++ b/utils/gpg_keys/stoffu.asc
@@ -0,0 +1,62 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFi08i4BEADYSPbRfJ4JEopmiYEzc4ipaEVu8yPJhX1sDy2Bad5FrzIFSuxU
+ppcOxxsKjDBpd/OlDEAcf4RoCnWWBd7W9dEYxjHHl3rCCvQkgkD+kTXKu8piNzki
+N6rCHNvpnbibicl/FYlNJsVCicLv2XS0GXEJnQeLEOvXLfl6iq3G31YcyRFKVKoE
+t8+dR3B4pXIjgM4aRDcFiZcLqdzO4PIvouE6E/yqjuVDH4Y1MkEwFTw+q4+UPpzB
+z6OpM+3we89me4OwEcj073N71QwneRl1I5Hltx/rJhBrHxHk0JEboj080QwGl3iw
+a+weFLtWuqvwt+AsBOyiZpBOCEeiihZ6W5fWAKvVwSwQEimRF6loQdWrXriM8vfh
+txZB0AqT/W/lNHVK/6pg8c3p+GREoEC0OySNymPYN6p8QEFCt1RhrhiUKTczkMFJ
+g7t0rm06mPdy2Dm4OfkAeMmUPdlF5R8PSoQMmXcNu/Y0JayG5yuwROVndPiZnX6u
+t/sjWpYavMuBoXwtQzP7PTNr1FDAIi/S7pwCdBSY+lqIG+u2qNimL6zXgay8WT6f
+1CisFzqx0n81uhJc0buprsKKKZFaeHIs8ZJPj9Y3JI5jBHALAgJX/VIFOIN+4cWa
+vaB+wB1sCenq2GvkCljdb8/XXuEtT5macaUAFkUem8U8JuotAdC5myaqKwARAQAB
+tB1zdG9mZnUgPHN0b2ZmdUBwcm90b25tYWlsLmNoPokCOAQTAQIAIgUCWLTyLgIb
+AwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQQdq4NDqewBLY4hAAkaIiEuhi
+MDJ8R186OP8rk508gfz7Lw9Usj7MibLN8WWOvaqD+fWozH+J/ThyrWvBAfzr93xC
+CWYYXunmUkeUHRkoY6djnJBjFfGQl5Yhil+bzE5foZjjs7nh4oaMc77TvaZqZfAg
+r/DO79Qf12M7t4vR1EhRmSfmeo1IvgwxJqhnCiYrr+M/cSk9ywnsdgGa9zdHyHyl
+IthYPrqPg9kQrJPfv6NWnesYULa1GVhC/HLbeO/TIn4c+G97FF7RSm8Z/o9oAZbR
+Acka56HIDqM9kM6pWeDHzZalzOiAII8+izx7KbijltWTdjns0BeWuW1/ylq+kFQ+
+NhDxe8XIBvKc3EiypLrjND8JinY9Vq5TVSYGqw6BM+0PE+38El7a81Qg2saPWqDO
+rPBx31CsDC88EeCTA/FgJKLOBdMjg6YHEjU9FioRQuifNis6hvfLhQJAPB2SgQl+
+ql7jShdIpPmwWaK5t/lRtGiaIyAMd4/PK3tjEV9CwB8hQhmUykJ/OjK0sLXg7aOX
+nBTBhvZtrHUCI5tFgKDQMV6lHq1WFPIYj2Ah7Zn2MuyxVk+LB8aDPqIODMVO1ehO
+U23lW2Xo64nEZS2+KwRk7A+o8/FDF1j2iDbCKm8xqOrmj2lu4nPU0MIdP3Y6xYUm
+Y4C57G2tFelV6k//H4ZACRfpgvvLkrzaDhGJAhwEEAECAAYFAljtzhcACgkQCF0J
+Lx9D1Royvw/7BBfxl9fvq+UtNu4SK+GD8C8pnznoRL1A1t6CtboG7c1TEhpCPxIu
+XHnBs74D5ZR+kDDsJegH+gYsvX9JlB+Il75DA5IYTByvijhSBXHFHKxdXnmBa2l3
+hb1yn8sxkU0jT5HpgrdY2GH93NPnfrJ4KcvINQtDEIcLwYRcQA4i4kujK55w9Q71
+s/+23wkldmSub9PDX62S3DoK37Wqn+EIUbvRrXvU9GmHLgnNiyoTy3IhrdiXRRAm
+Kei999ooJe8q3LgCcRg5mUshIV8RvcfJZOWjk4TCHNxRTZ2aaOakPrj87dH53LYS
+Zl3q86CvcVk8tujY+YSGb6RviKydHDZRQkvNk5WQReVa5C39qakF98lK+5DGXqNi
+mdtQKGO62Dr6OS/85ATzMJ8LZK6CkGMq8lwigtnJkgRFbZqhQ1/GtlKf3Qdrf9uh
+1Ds1U/hzeXLWskIXNpYcMmFISaYlEu7fyU7JAPq7otuQWMJPJHvNVHt3OTMqz3kR
+LfJxW8gq175GeJagOwjeBNQOTvvfyRqO1QtmZnoovvciH7MTLfYRPhScbtfAeTmx
+UR8Lqd7OssmeD95tGmF9fG9U4JsCfSE78x5mtAWdKj725T4I4QHdX7LSxeFOrCn/
+IjrM9NMW+iJpH5RguPPRyYRlgR2UvFkA3Y8b2qIA40ZXzdAc0slwDpy5Ag0EWLTy
+LgEQAL8SMWnZfiNXIaGe/yi8dI2gyAtafClcleSRwk3ehaBsb6jvYlpHdn8F7kSi
+jwyqu55I8fub0gtQ57GYz9rwar0E7l7USWC2GbpEs0AITCB8zDzNObW2yTmm12lS
+Vj+G5YvoqIH0ktf1I6QYZSV+E36EqX09SlPdkNk08JjSzafGrs1i3ImKDsdge4ca
+OelIDTm/x44sUi4AuG9fdaNzigUzTZxAZWTeH1FK55WJCacUizAFvNoDo5PcL0eB
+yLuh276f3Lj6PzMN94uLJQPsf52hfr41tZI3S7+9F1hX0hBdh8N11VKwx27lMRnt
+iOvkeFCGjta5VX/QSAShX1FPUqG0eyAd00RgiaBLJIoqiO6Z0VU6aMnC3Ce/MWPh
+oenYSjm7vVGoxGs192tW9WDsvgYINn/6GHMF5pXwZP1JIlt13l3TqUcuduafyu+B
+KseIUCnsxvW0DG1jVipc6rrEFusAfNRDHChxdnMKjcA87dLJ22tsbVE85dBXiMy5
+Setsa/mJC5w9/dUc2IOsOB+y4GVP/aTiRZRfcWHSPCQlytqKf1CT52ySctCb4LWZ
++lKbnIvQBr6CS+BKYnBsT/6QPdNfjT4HPIT8S2DOow7Ggrwaxp5kmzKU0dbqu8Hy
+s1f5lIjnr1pFWUL/fQKrIz/E5o/LfNhHXxVuwdKZmC+Lt8aRABEBAAGJAh8EGAEC
+AAkFAli08i4CGwwACgkQQdq4NDqewBLlqw/+N1zKL3m/UMRZOYMHGzkkQXwLIN3i
+u2XE+iLb11Lh6jKLUMb9k/NG4tX/xmXSJZhA/tHR0tSRH2s4Yv02k4pFQbABZjAD
+tUZly4TJ2OlBWCFE/Jr2HZFtL8xJpus0Qqv8eJFehsc1X/W6obl2sj6DLcglYOWL
+0WiSi4ITQvIyg2JKxwe2wea1RWIINpMBi1JfL/wl8P61y3HouEeDVcDXvKR3FC/7
+h7YGKqvfMWwZEu/f6kjA49PGiFOYhGGW3D4fBuVUKMmsoAhrUtrMEu+7wCA/Aekx
+ZO90IcPk/733KkfPpAkvre3o50JgJnXUBe0ZYXYZn/yrSE0rnF4ydfsQ7d2y+YEf
+8Iwmd7zulP750mGoMDJp09jqkrrj5VL9VAQn00cSFb4TPtdgcQyHWJ50SX8uuDJx
+e5ECxj+5hFntjWpFG+p4v1xedTfIQLhPtO4OXljOFzUYVzhMk/pX2oFcFoT6+AOt
+BPX5Rxw0bTsUnsnkD24OEhOaxiK/aAkummNT8hAK09NV/+rXHZZUL1n93SobnGJ/
+X4DlBRYA/tbn/Ol3vYzhWFzCDLrTwVsm8cTgY4bp+8I9ysUee5Rx4rw0qiyMxz6Z
+zRBUCxarimrjaoaejm6pm1yckEj2gBlf0nOaSiWgQhSTOq82Og0oVZT4ol8aDUv/
+hnbUrKukLHjFgd8=
+=GKYE
+-----END PGP PUBLIC KEY BLOCK-----