aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp4
-rw-r--r--src/common/util.cpp160
-rw-r--r--src/common/util.h4
-rw-r--r--src/crypto/CryptonightR_JIT.c6
-rw-r--r--src/crypto/crypto.cpp17
-rw-r--r--src/crypto/crypto.h1
-rw-r--r--src/crypto/random.c15
-rw-r--r--src/crypto/random.h1
-rw-r--r--src/crypto/slow-hash.c6
-rw-r--r--src/cryptonote_basic/connection_context.h2
-rw-r--r--src/cryptonote_basic/difficulty.h1
-rw-r--r--src/cryptonote_config.h10
-rw-r--r--src/cryptonote_core/blockchain.cpp3
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.h15
-rw-r--r--src/cryptonote_core/tx_sanity_check.cpp4
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl72
-rw-r--r--src/cryptonote_protocol/levin_notify.cpp574
-rw-r--r--src/cryptonote_protocol/levin_notify.h132
-rw-r--r--src/daemon/command_parser_executor.cpp14
-rw-r--r--src/daemon/command_parser_executor.h2
-rw-r--r--src/daemon/command_server.cpp7
-rw-r--r--src/daemon/main.cpp8
-rw-r--r--src/daemon/rpc_command_executor.cpp38
-rw-r--r--src/daemon/rpc_command_executor.h5
-rw-r--r--src/device/device_io_hid.cpp3
-rw-r--r--src/device/device_ledger.cpp6
-rw-r--r--src/device/device_ledger.hpp2
-rw-r--r--src/net/CMakeLists.txt4
-rw-r--r--src/net/dandelionpp.cpp212
-rw-r--r--src/net/dandelionpp.h106
-rw-r--r--src/net/parse.cpp2
-rw-r--r--src/p2p/net_node.cpp25
-rw-r--r--src/p2p/net_node.h13
-rw-r--r--src/p2p/net_node.inl136
-rw-r--r--src/p2p/net_node_common.h13
-rw-r--r--src/p2p/net_peerlist_boost_serialization.h2
-rw-r--r--src/ringct/bulletproofs.cc5
-rw-r--r--src/ringct/rctOps.cpp46
-rw-r--r--src/ringct/rctOps.h5
-rw-r--r--src/ringct/rctSigs.cpp75
-rw-r--r--src/rpc/CMakeLists.txt2
-rw-r--r--src/rpc/bootstrap_daemon.cpp95
-rw-r--r--src/rpc/bootstrap_daemon.h67
-rw-r--r--src/rpc/core_rpc_server.cpp112
-rw-r--r--src/rpc/core_rpc_server.h7
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h4
-rw-r--r--src/simplewallet/simplewallet.cpp559
-rw-r--r--src/simplewallet/simplewallet.h23
-rw-r--r--src/version.cpp.in2
-rw-r--r--src/version.h1
-rw-r--r--src/wallet/api/transaction_history.cpp2
-rw-r--r--src/wallet/message_store.cpp14
-rw-r--r--src/wallet/message_transporter.cpp61
-rw-r--r--src/wallet/message_transporter.h1
-rw-r--r--src/wallet/wallet2.cpp155
-rw-r--r--src/wallet/wallet2.h17
-rw-r--r--src/wallet/wallet_rpc_server.cpp6
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h4
58 files changed, 2418 insertions, 470 deletions
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 78db37b5a..760e380a9 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -595,7 +595,7 @@ bool BlockchainLMDB::need_resize(uint64_t threshold_size) const
MDEBUG("Space remaining: " << mei.me_mapsize - size_used);
MDEBUG("Size threshold: " << threshold_size);
float resize_percent = RESIZE_PERCENT;
- MDEBUG(boost::format("Percent used: %.04f Percent threshold: %.04f") % ((double)size_used/mei.me_mapsize) % resize_percent);
+ MDEBUG(boost::format("Percent used: %.04f Percent threshold: %.04f") % (100.*size_used/mei.me_mapsize) % (100.*resize_percent));
if (threshold_size > 0)
{
@@ -1329,7 +1329,7 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
// set up lmdb environment
if ((result = mdb_env_create(&m_env)))
throw0(DB_ERROR(lmdb_error("Failed to create lmdb environment: ", result).c_str()));
- if ((result = mdb_env_set_maxdbs(m_env, 20)))
+ if ((result = mdb_env_set_maxdbs(m_env, 32)))
throw0(DB_ERROR(lmdb_error("Failed to set max number of dbs: ", result).c_str()));
int threads = tools::get_max_concurrency();
diff --git a/src/common/util.cpp b/src/common/util.cpp
index 0fa9e8dc1..57e747837 100644
--- a/src/common/util.cpp
+++ b/src/common/util.cpp
@@ -30,6 +30,7 @@
#include <unistd.h>
#include <cstdio>
+#include <wchar.h>
#ifdef __GLIBC__
#include <gnu/libc-version.h>
@@ -67,6 +68,7 @@ using namespace epee;
#include "memwipe.h"
#include "cryptonote_config.h"
#include "net/http_client.h" // epee::net_utils::...
+#include "readline_buffer.h"
#ifdef WIN32
#ifndef STRSAFE_NO_DEPRECATE
@@ -1119,4 +1121,162 @@ std::string get_nix_version_display_string()
return (boost::format(size->format) % (double(bytes) / divisor)).str();
}
+ void clear_screen()
+ {
+ std::cout << "\033[2K" << std::flush; // clear whole line
+ std::cout << "\033c" << std::flush; // clear current screen and scrollback
+ std::cout << "\033[2J" << std::flush; // clear current screen only, scrollback is still around
+ std::cout << "\033[3J" << std::flush; // does nothing, should clear current screen and scrollback
+ std::cout << "\033[1;1H" << std::flush; // move cursor top/left
+ std::cout << "\r \r" << std::flush; // erase odd chars if the ANSI codes were printed raw
+#ifdef _WIN32
+ COORD coord{0, 0};
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (GetConsoleScreenBufferInfo(h, &csbi))
+ {
+ DWORD cbConSize = csbi.dwSize.X * csbi.dwSize.Y, w;
+ FillConsoleOutputCharacter(h, (TCHAR)' ', cbConSize, coord, &w);
+ if (GetConsoleScreenBufferInfo(h, &csbi))
+ FillConsoleOutputAttribute(h, csbi.wAttributes, cbConSize, coord, &w);
+ SetConsoleCursorPosition(h, coord);
+ }
+#endif
+ }
+
+ std::pair<std::string, size_t> get_string_prefix_by_width(const std::string &s, size_t columns)
+ {
+ std::string sc = "";
+ size_t avail = s.size();
+ const char *ptr = s.data();
+ wint_t cp = 0;
+ int bytes = 1;
+ size_t sw = 0;
+ char wbuf[8], *wptr;
+ while (avail--)
+ {
+ if ((*ptr & 0x80) == 0)
+ {
+ cp = *ptr++;
+ bytes = 1;
+ }
+ else if ((*ptr & 0xe0) == 0xc0)
+ {
+ if (avail < 1)
+ {
+ MERROR("Invalid UTF-8");
+ return std::make_pair(s, s.size());
+ }
+ cp = (*ptr++ & 0x1f) << 6;
+ cp |= *ptr++ & 0x3f;
+ --avail;
+ bytes = 2;
+ }
+ else if ((*ptr & 0xf0) == 0xe0)
+ {
+ if (avail < 2)
+ {
+ MERROR("Invalid UTF-8");
+ return std::make_pair(s, s.size());
+ }
+ cp = (*ptr++ & 0xf) << 12;
+ cp |= (*ptr++ & 0x3f) << 6;
+ cp |= *ptr++ & 0x3f;
+ avail -= 2;
+ bytes = 3;
+ }
+ else if ((*ptr & 0xf8) == 0xf0)
+ {
+ if (avail < 3)
+ {
+ MERROR("Invalid UTF-8");
+ return std::make_pair(s, s.size());
+ }
+ cp = (*ptr++ & 0x7) << 18;
+ cp |= (*ptr++ & 0x3f) << 12;
+ cp |= (*ptr++ & 0x3f) << 6;
+ cp |= *ptr++ & 0x3f;
+ avail -= 3;
+ bytes = 4;
+ }
+ else
+ {
+ MERROR("Invalid UTF-8");
+ return std::make_pair(s, s.size());
+ }
+
+ wptr = wbuf;
+ switch (bytes)
+ {
+ case 1: *wptr++ = cp; break;
+ case 2: *wptr++ = 0xc0 | (cp >> 6); *wptr++ = 0x80 | (cp & 0x3f); break;
+ case 3: *wptr++ = 0xe0 | (cp >> 12); *wptr++ = 0x80 | ((cp >> 6) & 0x3f); *wptr++ = 0x80 | (cp & 0x3f); break;
+ case 4: *wptr++ = 0xf0 | (cp >> 18); *wptr++ = 0x80 | ((cp >> 12) & 0x3f); *wptr++ = 0x80 | ((cp >> 6) & 0x3f); *wptr++ = 0x80 | (cp & 0x3f); break;
+ default: MERROR("Invalid UTF-8"); return std::make_pair(s, s.size());
+ }
+ *wptr = 0;
+ sc += std::string(wbuf, bytes);
+#ifdef _WIN32
+ int cpw = 1; // Guess who does not implement wcwidth
+#else
+ int cpw = wcwidth(cp);
+#endif
+ if (cpw > 0)
+ {
+ if (cpw > (int)columns)
+ break;
+ columns -= cpw;
+ sw += cpw;
+ }
+ cp = 0;
+ bytes = 1;
+ }
+ return std::make_pair(sc, sw);
+ }
+
+ size_t get_string_width(const std::string &s)
+ {
+ return get_string_prefix_by_width(s, 999999999).second;
+ };
+
+ std::vector<std::pair<std::string, size_t>> split_string_by_width(const std::string &s, size_t columns)
+ {
+ std::vector<std::string> words;
+ std::vector<std::pair<std::string, size_t>> lines;
+ boost::split(words, s, boost::is_any_of(" "), boost::token_compress_on);
+ // split large "words"
+ for (size_t i = 0; i < words.size(); ++i)
+ {
+ for (;;)
+ {
+ std::string prefix = get_string_prefix_by_width(words[i], columns).first;
+ if (prefix == words[i])
+ break;
+ words[i] = words[i].substr(prefix.size());
+ words.insert(words.begin() + i, prefix);
+ }
+ }
+
+ lines.push_back(std::make_pair("", 0));
+ while (!words.empty())
+ {
+ const size_t word_len = get_string_width(words.front());
+ size_t line_len = get_string_width(lines.back().first);
+ if (line_len > 0 && line_len + 1 + word_len > columns)
+ {
+ lines.push_back(std::make_pair("", 0));
+ line_len = 0;
+ }
+ if (line_len > 0)
+ {
+ lines.back().first += " ";
+ lines.back().second++;
+ }
+ lines.back().first += words.front();
+ lines.back().second += word_len;
+ words.erase(words.begin());
+ }
+ return lines;
+ }
+
}
diff --git a/src/common/util.h b/src/common/util.h
index b0f734eff..b794d7908 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -248,4 +248,8 @@ namespace tools
std::string get_human_readable_timespan(uint64_t seconds);
std::string get_human_readable_bytes(uint64_t bytes);
+
+ void clear_screen();
+
+ std::vector<std::pair<std::string, size_t>> split_string_by_width(const std::string &s, size_t columns);
}
diff --git a/src/crypto/CryptonightR_JIT.c b/src/crypto/CryptonightR_JIT.c
index ee8f3f36f..ffe7820a2 100644
--- a/src/crypto/CryptonightR_JIT.c
+++ b/src/crypto/CryptonightR_JIT.c
@@ -63,7 +63,7 @@ int v4_generate_JIT_code(const struct V4_Instruction* code, v4_random_math_JIT_f
#if !(defined(_MSC_VER) || defined(__MINGW32__))
if (mprotect((void*)buf, buf_size, PROT_READ | PROT_WRITE))
- return 1;
+ return -1;
#endif
APPEND_CODE(prologue, sizeof(prologue));
@@ -111,13 +111,13 @@ int v4_generate_JIT_code(const struct V4_Instruction* code, v4_random_math_JIT_f
#if !(defined(_MSC_VER) || defined(__MINGW32__))
if (mprotect((void*)buf, buf_size, PROT_READ | PROT_EXEC))
- return 1;
+ return -1;
#endif
__builtin___clear_cache((char*)buf, (char*)JIT_code);
return 0;
#else
- return 1;
+ return -1;
#endif
}
diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp
index 3f06c4f3f..0ec992de9 100644
--- a/src/crypto/crypto.cpp
+++ b/src/crypto/crypto.cpp
@@ -88,13 +88,24 @@ namespace crypto {
return &reinterpret_cast<const unsigned char &>(scalar);
}
- void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes)
+ boost::mutex &get_random_lock()
{
static boost::mutex random_lock;
- boost::lock_guard<boost::mutex> lock(random_lock);
+ return random_lock;
+ }
+
+ void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes)
+ {
+ boost::lock_guard<boost::mutex> lock(get_random_lock());
generate_random_bytes_not_thread_safe(N, bytes);
}
+ void add_extra_entropy_thread_safe(const void *ptr, size_t bytes)
+ {
+ boost::lock_guard<boost::mutex> lock(get_random_lock());
+ add_extra_entropy_not_thread_safe(ptr, bytes);
+ }
+
static inline bool less32(const unsigned char *k0, const unsigned char *k1)
{
for (int n = 31; n >= 0; --n)
@@ -275,8 +286,6 @@ namespace crypto {
buf.key = pub;
try_again:
random_scalar(k);
- if (((const uint32_t*)(&k))[7] == 0) // we don't want tiny numbers here
- goto try_again;
ge_scalarmult_base(&tmp3, &k);
ge_p3_tobytes(&buf.comm, &tmp3);
hash_to_scalar(&buf, sizeof(s_comm), sig.c);
diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h
index bac456f60..8ce321f71 100644
--- a/src/crypto/crypto.h
+++ b/src/crypto/crypto.h
@@ -147,6 +147,7 @@ namespace crypto {
};
void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes);
+ void add_extra_entropy_thread_safe(const void *ptr, size_t bytes);
/* Generate N random bytes
*/
diff --git a/src/crypto/random.c b/src/crypto/random.c
index 74b202661..766b5f558 100644
--- a/src/crypto/random.c
+++ b/src/crypto/random.c
@@ -146,3 +146,18 @@ void generate_random_bytes_not_thread_safe(size_t n, void *result) {
}
}
}
+
+void add_extra_entropy_not_thread_safe(const void *ptr, size_t bytes)
+{
+ size_t i;
+
+ while (bytes > 0)
+ {
+ hash_permutation(&state);
+ const size_t round_bytes = bytes > HASH_DATA_AREA ? HASH_DATA_AREA : bytes;
+ for (i = 0; i < round_bytes; ++i)
+ state.b[i] ^= ((const uint8_t*)ptr)[i];
+ bytes -= round_bytes;
+ ptr = cpadd(ptr, round_bytes);
+ }
+}
diff --git a/src/crypto/random.h b/src/crypto/random.h
index ccb9f4853..21a66d776 100644
--- a/src/crypto/random.h
+++ b/src/crypto/random.h
@@ -33,3 +33,4 @@
#include <stddef.h>
void generate_random_bytes_not_thread_safe(size_t n, void *result);
+void add_extra_entropy_not_thread_safe(const void *ptr, size_t bytes);
diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c
index 1fa819b57..647471513 100644
--- a/src/crypto/slow-hash.c
+++ b/src/crypto/slow-hash.c
@@ -136,8 +136,8 @@ static inline int use_v4_jit(void)
{ \
U64(b)[2] = state.hs.w[8] ^ state.hs.w[10]; \
U64(b)[3] = state.hs.w[9] ^ state.hs.w[11]; \
- division_result = state.hs.w[12]; \
- sqrt_result = state.hs.w[13]; \
+ division_result = SWAP64LE(state.hs.w[12]); \
+ sqrt_result = SWAP64LE(state.hs.w[13]); \
} while (0)
#define VARIANT2_PORTABLE_INIT() \
@@ -210,7 +210,7 @@ static inline int use_v4_jit(void)
uint64_t b0[2]; \
memcpy_swap64le(b0, b, 2); \
chunk2[0] = SWAP64LE(chunk1_old[0] + b0[0]); \
- chunk2[1] = SWAP64LE(SWAP64LE(chunk1_old[1]) + b0[1]); \
+ chunk2[1] = SWAP64LE(chunk1_old[1] + b0[1]); \
if (variant >= 4) \
{ \
uint64_t out_copy[2]; \
diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h
index 96398a90b..51076e8c0 100644
--- a/src/cryptonote_basic/connection_context.h
+++ b/src/cryptonote_basic/connection_context.h
@@ -31,8 +31,10 @@
#pragma once
#include <unordered_set>
#include <atomic>
+#include <boost/date_time/posix_time/posix_time.hpp>
#include "net/net_utils_base.h"
#include "copyable_atomic.h"
+#include "crypto/hash.h"
namespace cryptonote
{
diff --git a/src/cryptonote_basic/difficulty.h b/src/cryptonote_basic/difficulty.h
index 02ed89e5a..771deb04c 100644
--- a/src/cryptonote_basic/difficulty.h
+++ b/src/cryptonote_basic/difficulty.h
@@ -34,7 +34,6 @@
#include <vector>
#include <string>
#include <boost/multiprecision/cpp_int.hpp>
-
#include "crypto/hash.h"
namespace cryptonote
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index b68bb41e1..173b454f6 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -100,6 +100,16 @@
#define CRYPTONOTE_MEMPOOL_TX_LIVETIME (86400*3) //seconds, three days
#define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME 604800 //seconds, one week
+// see src/cryptonote_protocol/levin_notify.cpp
+#define CRYPTONOTE_NOISE_MIN_EPOCH 5 // minutes
+#define CRYPTONOTE_NOISE_EPOCH_RANGE 30 // seconds
+#define CRYPTONOTE_NOISE_MIN_DELAY 10 // seconds
+#define CRYPTONOTE_NOISE_DELAY_RANGE 5 // seconds
+#define CRYPTONOTE_NOISE_BYTES 3*1024 // 3 KiB
+#define CRYPTONOTE_NOISE_CHANNELS 2 // Max outgoing connections per zone used for noise/covert sending
+
+#define CRYPTONOTE_MAX_FRAGMENTS 20 // ~20 * NOISE_BYTES max payload size for covert/noise send
+
#define COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT 1000
#define P2P_LOCAL_WHITE_PEERLIST_LIMIT 1000
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index dab1a0a96..8ed0e526a 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -1738,7 +1738,8 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
const uint64_t prev_height = alt_chain.size() ? prev_data.height : m_db->get_block_height(b.prev_id);
bei.height = prev_height + 1;
uint64_t block_reward = get_outs_money_amount(b.miner_tx);
- bei.already_generated_coins = block_reward + (alt_chain.size() ? prev_data.already_generated_coins : m_db->get_block_already_generated_coins(prev_height));
+ const uint64_t prev_generated_coins = alt_chain.size() ? prev_data.already_generated_coins : m_db->get_block_already_generated_coins(prev_height);
+ bei.already_generated_coins = (block_reward < (MONEY_SUPPLY - prev_generated_coins)) ? prev_generated_coins + block_reward : MONEY_SUPPLY;
// verify that the block's timestamp is within the acceptable range
// (not earlier than the median of the last X blocks)
diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h
index d38aa7474..b03eb6e70 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.h
+++ b/src/cryptonote_core/cryptonote_tx_utils.h
@@ -83,6 +83,21 @@ namespace cryptonote
tx_destination_entry(uint64_t a, const account_public_address &ad, bool is_subaddress) : amount(a), addr(ad), is_subaddress(is_subaddress), is_integrated(false) { }
tx_destination_entry(const std::string &o, uint64_t a, const account_public_address &ad, bool is_subaddress) : original(o), amount(a), addr(ad), is_subaddress(is_subaddress), is_integrated(false) { }
+ std::string address(network_type nettype, const crypto::hash &payment_id) const
+ {
+ if (!original.empty())
+ {
+ return original;
+ }
+
+ if (is_integrated)
+ {
+ return get_account_integrated_address_as_str(nettype, addr, reinterpret_cast<const crypto::hash8 &>(payment_id));
+ }
+
+ return get_account_address_as_str(nettype, is_subaddress, addr);
+ }
+
BEGIN_SERIALIZE_OBJECT()
FIELD(original)
VARINT_FIELD(amount)
diff --git a/src/cryptonote_core/tx_sanity_check.cpp b/src/cryptonote_core/tx_sanity_check.cpp
index e95350f76..03cbb5c26 100644
--- a/src/cryptonote_core/tx_sanity_check.cpp
+++ b/src/cryptonote_core/tx_sanity_check.cpp
@@ -82,7 +82,7 @@ bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob
if (rct_indices.size() < n_indices * 8 / 10)
{
- MERROR("unique indices is only " << rct_indices.size() << "/" << n_indices);
+ MERROR("amount of unique indices is too low (amount of rct indices is " << rct_indices.size() << ", out of total " << n_indices << "indices.");
return false;
}
@@ -90,7 +90,7 @@ bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob
uint64_t median = epee::misc_utils::median(offsets);
if (median < n_available * 6 / 10)
{
- MERROR("median is " << median << "/" << n_available);
+ MERROR("median offset index is too low (median is " << median << " out of total " << n_available << "offsets). Transactions should contain a higher fraction of recent outputs.");
return false;
}
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index a1fa9484c..65115ee72 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -342,7 +342,7 @@ namespace cryptonote
if(m_core.have_block(hshd.top_id))
{
- if (target > hshd.current_height)
+ if (target > m_core.get_current_blockchain_height())
{
MINFO(context << "peer is not ahead of us and we're syncing, disconnecting");
return false;
@@ -438,7 +438,7 @@ namespace cryptonote
MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)");
if(context.m_state != cryptonote_connection_context::state_normal)
return 1;
- if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks
+ if(!is_synchronized() || m_no_sync) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks
{
LOG_DEBUG_CC(context, "Received new block while syncing, ignored");
return 1;
@@ -508,7 +508,7 @@ namespace cryptonote
MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_FLUFFY_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)");
if(context.m_state != cryptonote_connection_context::state_normal)
return 1;
- if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks
+ if(!is_synchronized() || m_no_sync) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks
{
LOG_DEBUG_CC(context, "Received new block while syncing, ignored");
return 1;
@@ -899,7 +899,7 @@ namespace cryptonote
// while syncing, core will lock for a long time, so we ignore
// those txes as they aren't really needed anyway, and avoid a
// long block before replying
- if(!is_synchronized())
+ if(!is_synchronized() || m_no_sync)
{
LOG_DEBUG_CC(context, "Received new tx while syncing, ignored");
return 1;
@@ -2227,69 +2227,11 @@ skip:
template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context)
{
- const bool hide_tx_broadcast =
- 1 < m_p2p->get_zone_count() && exclude_context.m_remote_address.get_zone() == epee::net_utils::zone::invalid;
-
- if (hide_tx_broadcast)
- MDEBUG("Attempting to conceal origin of tx via anonymity network connection(s)");
+ for(auto& tx_blob : arg.txs)
+ m_core.on_transaction_relayed(tx_blob);
// no check for success, so tell core they're relayed unconditionally
- const bool pad_transactions = m_core.pad_transactions() || hide_tx_broadcast;
- size_t bytes = pad_transactions ? 9 /* header */ + 4 /* 1 + 'txs' */ + tools::get_varint_data(arg.txs.size()).size() : 0;
- for(auto tx_blob_it = arg.txs.begin(); tx_blob_it!=arg.txs.end(); ++tx_blob_it)
- {
- m_core.on_transaction_relayed(*tx_blob_it);
- if (pad_transactions)
- bytes += tools::get_varint_data(tx_blob_it->size()).size() + tx_blob_it->size();
- }
-
- if (pad_transactions)
- {
- // stuff some dummy bytes in to stay safe from traffic volume analysis
- static constexpr size_t granularity = 1024;
- size_t padding = granularity - bytes % granularity;
- const size_t overhead = 2 /* 1 + '_' */ + tools::get_varint_data(padding).size();
- if (overhead > padding)
- padding = 0;
- else
- padding -= overhead;
- arg._ = std::string(padding, ' ');
-
- std::string arg_buff;
- epee::serialization::store_t_to_binary(arg, arg_buff);
-
- // we probably lowballed the payload size a bit, so added a but too much. Fix this now.
- size_t remove = arg_buff.size() % granularity;
- if (remove > arg._.size())
- arg._.clear();
- else
- arg._.resize(arg._.size() - remove);
- // if the size of _ moved enough, we might lose byte in size encoding, we don't care
- }
-
- std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections;
- m_p2p->for_each_connection([hide_tx_broadcast, &exclude_context, &connections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)
- {
- const epee::net_utils::zone current_zone = context.m_remote_address.get_zone();
- const bool broadcast_to_peer =
- peer_id &&
- (hide_tx_broadcast != bool(current_zone == epee::net_utils::zone::public_)) &&
- exclude_context.m_connection_id != context.m_connection_id;
-
- if (broadcast_to_peer)
- connections.push_back({current_zone, context.m_connection_id});
-
- return true;
- });
-
- if (connections.empty())
- MERROR("Transaction not relayed - no" << (hide_tx_broadcast ? " privacy": "") << " peers available");
- else
- {
- std::string fullBlob;
- epee::serialization::store_t_to_binary(arg, fullBlob);
- m_p2p->relay_notify_to_list(NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<uint8_t>(fullBlob), std::move(connections));
- }
+ m_p2p->send_txs(std::move(arg.txs), exclude_context.m_remote_address.get_zone(), exclude_context.m_connection_id, m_core.pad_transactions());
return true;
}
//------------------------------------------------------------------------------------------------------------------------
diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp
new file mode 100644
index 000000000..26cd93b5a
--- /dev/null
+++ b/src/cryptonote_protocol/levin_notify.cpp
@@ -0,0 +1,574 @@
+// Copyright (c) 2019, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "levin_notify.h"
+
+#include <boost/asio/steady_timer.hpp>
+#include <boost/system/system_error.hpp>
+#include <chrono>
+#include <deque>
+#include <stdexcept>
+
+#include "common/expect.h"
+#include "common/varint.h"
+#include "cryptonote_config.h"
+#include "crypto/random.h"
+#include "cryptonote_basic/connection_context.h"
+#include "cryptonote_protocol/cryptonote_protocol_defs.h"
+#include "net/dandelionpp.h"
+#include "p2p/net_node.h"
+
+namespace cryptonote
+{
+namespace levin
+{
+ namespace
+ {
+ constexpr std::size_t connection_id_reserve_size = 100;
+
+ constexpr const std::chrono::minutes noise_min_epoch{CRYPTONOTE_NOISE_MIN_EPOCH};
+ constexpr const std::chrono::seconds noise_epoch_range{CRYPTONOTE_NOISE_EPOCH_RANGE};
+
+ constexpr const std::chrono::seconds noise_min_delay{CRYPTONOTE_NOISE_MIN_DELAY};
+ constexpr const std::chrono::seconds noise_delay_range{CRYPTONOTE_NOISE_DELAY_RANGE};
+
+ /*! Select a randomized duration from 0 to `range`. The precision will be to
+ the systems `steady_clock`. As an example, supplying 3 seconds to this
+ function will select a duration from [0, 3] seconds, and the increments
+ for the selection will be determined by the `steady_clock` precision
+ (typically nanoseconds).
+
+ \return A randomized duration from 0 to `range`. */
+ std::chrono::steady_clock::duration random_duration(std::chrono::steady_clock::duration range)
+ {
+ using rep = std::chrono::steady_clock::rep;
+ return std::chrono::steady_clock::duration{crypto::rand_range(rep(0), range.count())};
+ }
+
+ //! \return All outgoing connections supporting fragments in `connections`.
+ std::vector<boost::uuids::uuid> get_out_connections(connections& p2p)
+ {
+ std::vector<boost::uuids::uuid> outs;
+ outs.reserve(connection_id_reserve_size);
+
+ /* The foreach call is serialized with a lock, but should be quick due to
+ the reserve call so a strand is not used. Investigate if there is lots
+ of waiting in here. */
+
+ p2p.foreach_connection([&outs] (detail::p2p_context& context) {
+ if (!context.m_is_income)
+ outs.emplace_back(context.m_connection_id);
+ return true;
+ });
+
+ return outs;
+ }
+
+ std::string make_tx_payload(std::vector<blobdata>&& txs, const bool pad)
+ {
+ NOTIFY_NEW_TRANSACTIONS::request request{};
+ request.txs = std::move(txs);
+
+ if (pad)
+ {
+ size_t bytes = 9 /* header */ + 4 /* 1 + 'txs' */ + tools::get_varint_data(request.txs.size()).size();
+ for(auto tx_blob_it = request.txs.begin(); tx_blob_it!=request.txs.end(); ++tx_blob_it)
+ bytes += tools::get_varint_data(tx_blob_it->size()).size() + tx_blob_it->size();
+
+ // stuff some dummy bytes in to stay safe from traffic volume analysis
+ static constexpr const size_t granularity = 1024;
+ size_t padding = granularity - bytes % granularity;
+ const size_t overhead = 2 /* 1 + '_' */ + tools::get_varint_data(padding).size();
+ if (overhead > padding)
+ padding = 0;
+ else
+ padding -= overhead;
+ request._ = std::string(padding, ' ');
+
+ std::string arg_buff;
+ epee::serialization::store_t_to_binary(request, arg_buff);
+
+ // we probably lowballed the payload size a bit, so added a but too much. Fix this now.
+ size_t remove = arg_buff.size() % granularity;
+ if (remove > request._.size())
+ request._.clear();
+ else
+ request._.resize(request._.size() - remove);
+ // if the size of _ moved enough, we might lose byte in size encoding, we don't care
+ }
+
+ std::string fullBlob;
+ if (!epee::serialization::store_t_to_binary(request, fullBlob))
+ throw std::runtime_error{"Failed to serialize to epee binary format"};
+
+ return fullBlob;
+ }
+
+ /* The current design uses `asio::strand`s. The documentation isn't as clear
+ as it should be - a `strand` has an internal `mutex` and `bool`. The
+ `mutex` synchronizes thread access and the `bool` is set when a thread is
+ executing something "in the strand". Therefore, if a callback has lots of
+ work to do in a `strand`, asio can switch to some other task instead of
+ blocking 1+ threads to wait for the original thread to complete the task
+ (as is the case when client code has a `mutex` inside the callback). The
+ downside is that asio _always_ allocates for the callback, even if it can
+ be immediately executed. So if all work in a strand is minimal, a lock
+ may be better.
+
+ This code uses a strand per "zone" and a strand per "channel in a zone".
+ `dispatch` is used heavily, which means "execute immediately in _this_
+ thread if the strand is not in use, otherwise queue the callback to be
+ executed immediately after the strand completes its current task".
+ `post` is used where deferred execution to an `asio::io_service::run`
+ thread is preferred.
+
+ The strand per "zone" is useful because the levin
+ `foreach_connection` is blocked with a mutex anyway. So this primarily
+ helps with reducing blocking of a thread attempting a "flood"
+ notification. Updating/merging the outgoing connections in the
+ Dandelion++ map is also somewhat expensive.
+
+ The strand per "channel" may need a re-visit. The most "expensive" code
+ is figuring out the noise/notification to send. If levin code is
+ optimized further, it might be better to just use standard locks per
+ channel. */
+
+ //! A queue of levin messages for a noise i2p/tor link
+ struct noise_channel
+ {
+ explicit noise_channel(boost::asio::io_service& io_service)
+ : active(nullptr),
+ queue(),
+ strand(io_service),
+ next_noise(io_service),
+ connection(boost::uuids::nil_uuid())
+ {}
+
+ // `asio::io_service::strand` cannot be copied or moved
+ noise_channel(const noise_channel&) = delete;
+ noise_channel& operator=(const noise_channel&) = delete;
+
+ // Only read/write these values "inside the strand"
+
+ epee::byte_slice active;
+ std::deque<epee::byte_slice> queue;
+ boost::asio::io_service::strand strand;
+ boost::asio::steady_timer next_noise;
+ boost::uuids::uuid connection;
+ };
+ } // anonymous
+
+ namespace detail
+ {
+ struct zone
+ {
+ explicit zone(boost::asio::io_service& io_service, std::shared_ptr<connections> p2p, epee::byte_slice noise_in)
+ : p2p(std::move(p2p)),
+ noise(std::move(noise_in)),
+ next_epoch(io_service),
+ strand(io_service),
+ map(),
+ channels(),
+ connection_count(0)
+ {
+ for (std::size_t count = 0; !noise.empty() && count < CRYPTONOTE_NOISE_CHANNELS; ++count)
+ channels.emplace_back(io_service);
+ }
+
+ const std::shared_ptr<connections> p2p;
+ const epee::byte_slice noise; //!< `!empty()` means zone is using noise channels
+ boost::asio::steady_timer next_epoch;
+ boost::asio::io_service::strand strand;
+ net::dandelionpp::connection_map map;//!< Tracks outgoing uuid's for noise channels or Dandelion++ stems
+ std::deque<noise_channel> channels; //!< Never touch after init; only update elements on `noise_channel.strand`
+ std::atomic<std::size_t> connection_count; //!< Only update in strand, can be read at any time
+ };
+ } // detail
+
+ namespace
+ {
+ //! Adds a message to the sending queue of the channel.
+ class queue_covert_notify
+ {
+ std::shared_ptr<detail::zone> zone_;
+ epee::byte_slice message_; // Requires manual copy constructor
+ const std::size_t destination_;
+
+ public:
+ queue_covert_notify(std::shared_ptr<detail::zone> zone, epee::byte_slice message, std::size_t destination)
+ : zone_(std::move(zone)), message_(std::move(message)), destination_(destination)
+ {}
+
+ queue_covert_notify(queue_covert_notify&&) = default;
+ queue_covert_notify(const queue_covert_notify& source)
+ : zone_(source.zone_), message_(source.message_.clone()), destination_(source.destination_)
+ {}
+
+ //! \pre Called within `zone_->channels[destionation_].strand`.
+ void operator()()
+ {
+ if (!zone_)
+ return;
+
+ noise_channel& channel = zone_->channels.at(destination_);
+ assert(channel.strand.running_in_this_thread());
+
+ if (!channel.connection.is_nil())
+ channel.queue.push_back(std::move(message_));
+ }
+ };
+
+ //! Sends a message to every active connection
+ class flood_notify
+ {
+ std::shared_ptr<detail::zone> zone_;
+ epee::byte_slice message_; // Requires manual copy
+ boost::uuids::uuid source_;
+
+ public:
+ explicit flood_notify(std::shared_ptr<detail::zone> zone, epee::byte_slice message, const boost::uuids::uuid& source)
+ : zone_(std::move(zone)), message_(message.clone()), source_(source)
+ {}
+
+ flood_notify(flood_notify&&) = default;
+ flood_notify(const flood_notify& source)
+ : zone_(source.zone_), message_(source.message_.clone()), source_(source.source_)
+ {}
+
+ void operator()() const
+ {
+ if (!zone_ || !zone_->p2p)
+ return;
+
+ assert(zone_->strand.running_in_this_thread());
+
+ /* The foreach should be quick, but then it iterates and acquires the
+ same lock for every connection. So do in a strand because two threads
+ will ping-pong each other with cacheline invalidations. Revisit if
+ algorithm changes or the locking strategy within the levin config
+ class changes. */
+
+ std::vector<boost::uuids::uuid> connections;
+ connections.reserve(connection_id_reserve_size);
+ zone_->p2p->foreach_connection([this, &connections] (detail::p2p_context& context) {
+ if (this->source_ != context.m_connection_id)
+ connections.emplace_back(context.m_connection_id);
+ return true;
+ });
+
+ for (const boost::uuids::uuid& connection : connections)
+ zone_->p2p->send(message_.clone(), connection);
+ }
+ };
+
+ //! Updates the connection for a channel.
+ struct update_channel
+ {
+ std::shared_ptr<detail::zone> zone_;
+ const std::size_t channel_;
+ const boost::uuids::uuid connection_;
+
+ //! \pre Called within `stem_.strand`.
+ void operator()() const
+ {
+ if (!zone_)
+ return;
+
+ noise_channel& channel = zone_->channels.at(channel_);
+ assert(channel.strand.running_in_this_thread());
+ static_assert(
+ CRYPTONOTE_MAX_FRAGMENTS <= (noise_min_epoch / (noise_min_delay + noise_delay_range)),
+ "Max fragments more than the max that can be sent in an epoch"
+ );
+
+ /* This clears the active message so that a message "in-flight" is
+ restarted. DO NOT try to send the remainder of the fragments, this
+ additional send time can leak that this node was sending out a real
+ notify (tx) instead of dummy noise. */
+
+ channel.connection = connection_;
+ channel.active = nullptr;
+
+ if (connection_.is_nil())
+ channel.queue.clear();
+ }
+ };
+
+ //! Merges `out_connections_` into the existing `zone_->map`.
+ struct update_channels
+ {
+ std::shared_ptr<detail::zone> zone_;
+ std::vector<boost::uuids::uuid> out_connections_;
+
+ //! \pre Called within `zone->strand`.
+ static void post(std::shared_ptr<detail::zone> zone)
+ {
+ if (!zone)
+ return;
+
+ assert(zone->strand.running_in_this_thread());
+
+ zone->connection_count = zone->map.size();
+ for (auto id = zone->map.begin(); id != zone->map.end(); ++id)
+ {
+ const std::size_t i = id - zone->map.begin();
+ zone->channels[i].strand.post(update_channel{zone, i, *id});
+ }
+ }
+
+ //! \pre Called within `zone_->strand`.
+ void operator()()
+ {
+ if (!zone_)
+ return;
+
+ assert(zone_->strand.running_in_this_thread());
+ if (zone_->map.update(std::move(out_connections_)))
+ post(std::move(zone_));
+ }
+ };
+
+ //! Swaps out noise channels entirely; new epoch start.
+ class change_channels
+ {
+ std::shared_ptr<detail::zone> zone_;
+ net::dandelionpp::connection_map map_; // Requires manual copy constructor
+
+ public:
+ explicit change_channels(std::shared_ptr<detail::zone> zone, net::dandelionpp::connection_map map)
+ : zone_(std::move(zone)), map_(std::move(map))
+ {}
+
+ change_channels(change_channels&&) = default;
+ change_channels(const change_channels& source)
+ : zone_(source.zone_), map_(source.map_.clone())
+ {}
+
+ //! \pre Called within `zone_->strand`.
+ void operator()()
+ {
+ if (!zone_)
+ return
+
+ assert(zone_->strand.running_in_this_thread());
+
+ zone_->map = std::move(map_);
+ update_channels::post(std::move(zone_));
+ }
+ };
+
+ //! Sends a noise packet or real notification and sets timer for next call.
+ struct send_noise
+ {
+ std::shared_ptr<detail::zone> zone_;
+ const std::size_t channel_;
+
+ static void wait(const std::chrono::steady_clock::time_point start, std::shared_ptr<detail::zone> zone, const std::size_t index)
+ {
+ if (!zone)
+ return;
+
+ noise_channel& channel = zone->channels.at(index);
+ channel.next_noise.expires_at(start + noise_min_delay + random_duration(noise_delay_range));
+ channel.next_noise.async_wait(
+ channel.strand.wrap(send_noise{std::move(zone), index})
+ );
+ }
+
+ //! \pre Called within `zone_->channels[channel_].strand`.
+ void operator()(boost::system::error_code error)
+ {
+ if (!zone_ || !zone_->p2p || zone_->noise.empty())
+ return;
+
+ if (error && error != boost::system::errc::operation_canceled)
+ throw boost::system::system_error{error, "send_noise timer failed"};
+
+ assert(zone_->channels.at(channel_).strand.running_in_this_thread());
+
+ const auto start = std::chrono::steady_clock::now();
+ noise_channel& channel = zone_->channels.at(channel_);
+
+ if (!channel.connection.is_nil())
+ {
+ epee::byte_slice message = nullptr;
+ if (!channel.active.empty())
+ message = channel.active.take_slice(zone_->noise.size());
+ else if (!channel.queue.empty())
+ {
+ channel.active = channel.queue.front().clone();
+ message = channel.active.take_slice(zone_->noise.size());
+ }
+ else
+ message = zone_->noise.clone();
+
+ if (zone_->p2p->send(std::move(message), channel.connection))
+ {
+ if (!channel.queue.empty() && channel.active.empty())
+ channel.queue.pop_front();
+ }
+ else
+ {
+ channel.active = nullptr;
+ channel.connection = boost::uuids::nil_uuid();
+ zone_->strand.post(
+ update_channels{zone_, get_out_connections(*zone_->p2p)}
+ );
+ }
+ }
+
+ wait(start, std::move(zone_), channel_);
+ }
+ };
+
+ //! Prepares connections for new channel epoch and sets timer for next epoch
+ struct start_epoch
+ {
+ // Variables allow for Dandelion++ extension
+ std::shared_ptr<detail::zone> zone_;
+ std::chrono::seconds min_epoch_;
+ std::chrono::seconds epoch_range_;
+ std::size_t count_;
+
+ //! \pre Should not be invoked within any strand to prevent blocking.
+ void operator()(const boost::system::error_code error = {})
+ {
+ if (!zone_ || !zone_->p2p)
+ return;
+
+ if (error && error != boost::system::errc::operation_canceled)
+ throw boost::system::system_error{error, "start_epoch timer failed"};
+
+ const auto start = std::chrono::steady_clock::now();
+ zone_->strand.dispatch(
+ change_channels{zone_, net::dandelionpp::connection_map{get_out_connections(*(zone_->p2p)), count_}}
+ );
+
+ detail::zone& alias = *zone_;
+ alias.next_epoch.expires_at(start + min_epoch_ + random_duration(epoch_range_));
+ alias.next_epoch.async_wait(start_epoch{std::move(*this)});
+ }
+ };
+ } // anonymous
+
+ notify::notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise)
+ : zone_(std::make_shared<detail::zone>(service, std::move(p2p), std::move(noise)))
+ {
+ if (!zone_->p2p)
+ throw std::logic_error{"cryptonote::levin::notify cannot have nullptr p2p argument"};
+
+ if (!zone_->noise.empty())
+ {
+ const auto now = std::chrono::steady_clock::now();
+ start_epoch{zone_, noise_min_epoch, noise_epoch_range, CRYPTONOTE_NOISE_CHANNELS}();
+ for (std::size_t channel = 0; channel < zone_->channels.size(); ++channel)
+ send_noise::wait(now, zone_, channel);
+ }
+ }
+
+ notify::~notify() noexcept
+ {}
+
+ notify::status notify::get_status() const noexcept
+ {
+ if (!zone_)
+ return {false, false};
+
+ return {!zone_->noise.empty(), CRYPTONOTE_NOISE_CHANNELS <= zone_->connection_count};
+ }
+
+ void notify::new_out_connection()
+ {
+ if (!zone_ || zone_->noise.empty() || CRYPTONOTE_NOISE_CHANNELS <= zone_->connection_count)
+ return;
+
+ zone_->strand.dispatch(
+ update_channels{zone_, get_out_connections(*(zone_->p2p))}
+ );
+ }
+
+ void notify::run_epoch()
+ {
+ if (!zone_)
+ return;
+ zone_->next_epoch.cancel();
+ }
+
+ void notify::run_stems()
+ {
+ if (!zone_)
+ return;
+
+ for (noise_channel& channel : zone_->channels)
+ channel.next_noise.cancel();
+ }
+
+ bool notify::send_txs(std::vector<cryptonote::blobdata> txs, const boost::uuids::uuid& source, const bool pad_txs)
+ {
+ if (!zone_)
+ return false;
+
+ if (!zone_->noise.empty() && !zone_->channels.empty())
+ {
+ // covert send in "noise" channel
+ static_assert(
+ CRYPTONOTE_MAX_FRAGMENTS * CRYPTONOTE_NOISE_BYTES <= LEVIN_DEFAULT_MAX_PACKET_SIZE, "most nodes will reject this fragment setting"
+ );
+
+ // padding is not useful when using noise mode
+ const std::string payload = make_tx_payload(std::move(txs), false);
+ epee::byte_slice message = epee::levin::make_fragmented_notify(
+ zone_->noise, NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<std::uint8_t>(payload)
+ );
+ if (CRYPTONOTE_MAX_FRAGMENTS * zone_->noise.size() < message.size())
+ {
+ MERROR("notify::send_txs provided message exceeding covert fragment size");
+ return false;
+ }
+
+ for (std::size_t channel = 0; channel < zone_->channels.size(); ++channel)
+ {
+ zone_->channels[channel].strand.dispatch(
+ queue_covert_notify{zone_, message.clone(), channel}
+ );
+ }
+ }
+ else
+ {
+ const std::string payload = make_tx_payload(std::move(txs), pad_txs);
+ epee::byte_slice message =
+ epee::levin::make_notify(NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<std::uint8_t>(payload));
+
+ // traditional monero send technique
+ zone_->strand.dispatch(flood_notify{zone_, std::move(message), source});
+ }
+
+ return true;
+ }
+} // levin
+} // net
diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h
new file mode 100644
index 000000000..82d22680a
--- /dev/null
+++ b/src/cryptonote_protocol/levin_notify.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2019, 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.
+
+#pragma once
+
+#include <boost/asio/io_service.hpp>
+#include <boost/uuid/uuid.hpp>
+#include <memory>
+#include <vector>
+
+#include "byte_slice.h"
+#include "cryptonote_basic/blobdatatype.h"
+#include "net/enums.h"
+#include "span.h"
+
+namespace epee
+{
+namespace levin
+{
+ template<typename> class async_protocol_handler_config;
+}
+}
+
+namespace nodetool
+{
+ template<typename> struct p2p_connection_context_t;
+}
+
+namespace cryptonote
+{
+ struct cryptonote_connection_context;
+}
+
+namespace cryptonote
+{
+namespace levin
+{
+ namespace detail
+ {
+ using p2p_context = nodetool::p2p_connection_context_t<cryptonote::cryptonote_connection_context>;
+ struct zone; //!< Internal data needed for zone notifications
+ } // detail
+
+ using connections = epee::levin::async_protocol_handler_config<detail::p2p_context>;
+
+ //! Provides tx notification privacy
+ class notify
+ {
+ std::shared_ptr<detail::zone> zone_;
+
+ public:
+ struct status
+ {
+ bool has_noise;
+ bool connections_filled;
+ };
+
+ //! Construct an instance that cannot notify.
+ notify() noexcept
+ : zone_(nullptr)
+ {}
+
+ //! Construct an instance with available notification `zones`.
+ explicit notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise);
+
+ notify(const notify&) = delete;
+ notify(notify&&) = default;
+
+ ~notify() noexcept;
+
+ notify& operator=(const notify&) = delete;
+ notify& operator=(notify&&) = default;
+
+ //! \return Status information for zone selection.
+ status get_status() const noexcept;
+
+ //! Probe for new outbound connection - skips if not needed.
+ void new_out_connection();
+
+ //! Run the logic for the next epoch immediately. Only use in testing.
+ void run_epoch();
+
+ //! Run the logic for the next stem timeout imemdiately. Only use in testing.
+ void run_stems();
+
+ /*! Send txs using `cryptonote_protocol_defs.h` payload format wrapped in a
+ levin header. The message will be sent in a "discreet" manner if
+ enabled - if `!noise.empty()` then the `command`/`payload` will be
+ queued to send at the next available noise interval. Otherwise, a
+ standard Monero flood notification will be used.
+
+ \note Eventually Dandelion++ stem sending will be used here when
+ enabled.
+
+ \param txs The transactions that need to be serialized and relayed.
+ \param source The source of the notification. `is_nil()` indicates this
+ node is the source. Dandelion++ will use this to map a source to a
+ particular stem.
+ \param pad_txs A request to pad txs to help conceal origin via
+ statistical analysis. Ignored if noise was enabled during
+ construction.
+
+ \return True iff the notification is queued for sending. */
+ bool send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source, bool pad_txs);
+ };
+} // levin
+} // net
diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp
index 924447701..c1e46d00a 100644
--- a/src/daemon/command_parser_executor.cpp
+++ b/src/daemon/command_parser_executor.cpp
@@ -813,4 +813,18 @@ bool t_command_parser_executor::check_blockchain_pruning(const std::vector<std::
return m_executor.check_blockchain_pruning();
}
+bool t_command_parser_executor::set_bootstrap_daemon(const std::vector<std::string>& args)
+{
+ const size_t args_count = args.size();
+ if (args_count < 1 || args_count > 3)
+ {
+ return false;
+ }
+
+ return m_executor.set_bootstrap_daemon(
+ args[0] != "none" ? args[0] : std::string(),
+ args_count > 1 ? args[1] : std::string(),
+ args_count > 2 ? args[2] : std::string());
+}
+
} // namespace daemonize
diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h
index d39bc1c9b..25587dea8 100644
--- a/src/daemon/command_parser_executor.h
+++ b/src/daemon/command_parser_executor.h
@@ -150,6 +150,8 @@ public:
bool check_blockchain_pruning(const std::vector<std::string>& args);
bool print_net_stats(const std::vector<std::string>& args);
+
+ bool set_bootstrap_daemon(const std::vector<std::string>& args);
};
} // namespace daemonize
diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp
index aecdda52c..757e072a4 100644
--- a/src/daemon/command_server.cpp
+++ b/src/daemon/command_server.cpp
@@ -310,6 +310,13 @@ t_command_server::t_command_server(
, std::bind(&t_command_parser_executor::check_blockchain_pruning, &m_parser, p::_1)
, "Check the blockchain pruning."
);
+ m_command_lookup.set_handler(
+ "set_bootstrap_daemon"
+ , std::bind(&t_command_parser_executor::set_bootstrap_daemon, &m_parser, p::_1)
+ , "set_bootstrap_daemon (auto | none | host[:port] [username] [password])"
+ , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced.\n"
+ "Use 'auto' to enable automatic public nodes discovering and bootstrap daemon switching"
+ );
}
bool t_command_server::process_command_str(const std::string& cmd)
diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp
index 461888062..cb288071e 100644
--- a/src/daemon/main.cpp
+++ b/src/daemon/main.cpp
@@ -312,9 +312,7 @@ int main(int argc, char const * argv[])
{
login = tools::login::parse(
has_rpc_arg ? command_line::get_arg(vm, arg.rpc_login) : std::string(env_rpc_login), false, [](bool verify) {
-#ifdef HAVE_READLINE
- rdln::suspend_readline pause_readline;
-#endif
+ PAUSE_READLINE();
return tools::password_container::prompt(verify, "Daemon client password");
}
);
@@ -336,9 +334,7 @@ int main(int argc, char const * argv[])
}
else
{
-#ifdef HAVE_READLINE
- rdln::suspend_readline pause_readline;
-#endif
+ PAUSE_READLINE();
std::cerr << "Unknown command: " << command.front() << std::endl;
return 1;
}
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index 4d3debed6..716dea204 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -66,7 +66,7 @@ namespace {
time(&now);
time_t last_seen = static_cast<time_t>(peer.last_seen);
- std::string elapsed = epee::misc_utils::get_time_interval_string(now - last_seen);
+ std::string elapsed = peer.last_seen == 0 ? "never" : epee::misc_utils::get_time_interval_string(now - last_seen);
std::string id_str = epee::string_tools::pad_string(epee::string_tools::to_string_hex(peer.id), 16, '0', true);
std::string port_str;
epee::string_tools::xtype_to_string(peer.port, port_str);
@@ -2320,4 +2320,40 @@ bool t_rpc_command_executor::check_blockchain_pruning()
return true;
}
+bool t_rpc_command_executor::set_bootstrap_daemon(
+ const std::string &address,
+ const std::string &username,
+ const std::string &password)
+{
+ cryptonote::COMMAND_RPC_SET_BOOTSTRAP_DAEMON::request req;
+ cryptonote::COMMAND_RPC_SET_BOOTSTRAP_DAEMON::response res;
+ const std::string fail_message = "Unsuccessful";
+
+ req.address = address;
+ req.username = username;
+ req.password = password;
+
+ if (m_is_rpc)
+ {
+ if (!m_rpc_client->rpc_request(req, res, "/set_bootstrap_daemon", fail_message))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (!m_rpc_server->on_set_bootstrap_daemon(req, res) || res.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << make_error(fail_message, res.status);
+ return true;
+ }
+ }
+
+ tools::success_msg_writer()
+ << "Successfully set bootstrap daemon address to "
+ << (!req.address.empty() ? req.address : "none");
+
+ return true;
+}
+
}// namespace daemonize
diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h
index f3ed48319..c474e0a0f 100644
--- a/src/daemon/rpc_command_executor.h
+++ b/src/daemon/rpc_command_executor.h
@@ -162,6 +162,11 @@ public:
bool check_blockchain_pruning();
bool print_net_stats();
+
+ bool set_bootstrap_daemon(
+ const std::string &address,
+ const std::string &username,
+ const std::string &password);
};
} // namespace daemonize
diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp
index 721bed9ca..72f4c3bdb 100644
--- a/src/device/device_io_hid.cpp
+++ b/src/device/device_io_hid.cpp
@@ -44,7 +44,8 @@ namespace hw {
static std::string safe_hid_error(hid_device *hwdev) {
if (hwdev) {
- return std::string((char*)hid_error(hwdev));
+ const char* error_str = (const char*)hid_error(hwdev);
+ return std::string(error_str == nullptr ? "Unknown error" : error_str);
}
return std::string("NULL device");
}
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
index eba633da8..15fe560d1 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -320,7 +320,9 @@ namespace hw {
bool device_ledger::reset() {
reset_buffer();
int offset = set_command_header_noopt(INS_RESET);
- memmove(this->buffer_send+offset, MONERO_VERSION, strlen(MONERO_VERSION));
+ const size_t verlen = strlen(MONERO_VERSION);
+ ASSERT_X(offset + verlen <= BUFFER_SEND_SIZE, "MONERO_VERSION is too long")
+ memmove(this->buffer_send+offset, MONERO_VERSION, verlen);
offset += strlen(MONERO_VERSION);
this->buffer_send[4] = offset-5;
this->length_send = offset;
@@ -416,10 +418,10 @@ namespace hw {
#ifdef DEBUG_HWDEVICE
cryptonote::account_public_address pubkey;
this->get_public_address(pubkey);
- #endif
crypto::secret_key vkey;
crypto::secret_key skey;
this->get_secret_keys(vkey,skey);
+ #endif
return true;
}
diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp
index fe9028733..986087128 100644
--- a/src/device/device_ledger.hpp
+++ b/src/device/device_ledger.hpp
@@ -76,7 +76,7 @@ namespace hw {
rct::key AKout;
ABPkeys(const rct::key& A, const rct::key& B, const bool is_subaddr, bool is_subaddress, bool is_change_address, size_t index, const rct::key& P,const rct::key& AK);
ABPkeys(const ABPkeys& keys) ;
- ABPkeys() {index=0;is_subaddress=false;is_subaddress=false;is_change_address=false;}
+ ABPkeys() {index=0;is_subaddress=false;is_change_address=false;additional_key=false;}
ABPkeys &operator=(const ABPkeys &keys);
};
diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt
index 738f858f0..24b707f77 100644
--- a/src/net/CMakeLists.txt
+++ b/src/net/CMakeLists.txt
@@ -26,8 +26,8 @@
# 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.
-set(net_sources error.cpp i2p_address.cpp parse.cpp socks.cpp socks_connect.cpp tor_address.cpp)
-set(net_headers error.h i2p_address.h parse.h socks.h socks_connect.h tor_address.h)
+set(net_sources dandelionpp.cpp error.cpp i2p_address.cpp parse.cpp socks.cpp socks_connect.cpp tor_address.cpp)
+set(net_headers dandelionpp.h error.h i2p_address.h parse.h socks.h socks_connect.h tor_address.h)
monero_add_library(net ${net_sources} ${net_headers})
target_link_libraries(net common epee ${Boost_ASIO_LIBRARY})
diff --git a/src/net/dandelionpp.cpp b/src/net/dandelionpp.cpp
new file mode 100644
index 000000000..4d2f75428
--- /dev/null
+++ b/src/net/dandelionpp.cpp
@@ -0,0 +1,212 @@
+// Copyright (c) 2019, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "dandelionpp.h"
+
+#include <boost/container/small_vector.hpp>
+#include <boost/uuid/nil_generator.hpp>
+#include <chrono>
+
+#include "common/expect.h"
+#include "cryptonote_config.h"
+#include "crypto/crypto.h"
+
+namespace net
+{
+namespace dandelionpp
+{
+ namespace
+ {
+ constexpr const std::size_t expected_max_channels = CRYPTONOTE_NOISE_CHANNELS;
+
+ // could be in util somewhere
+ struct key_less
+ {
+ template<typename K, typename V>
+ bool operator()(const std::pair<K, V>& left, const K& right) const
+ {
+ return left.first < right;
+ }
+
+ template<typename K, typename V>
+ bool operator()(const K& left, const std::pair<K, V>& right) const
+ {
+ return left < right.first;
+ }
+ };
+
+ std::size_t select_stem(epee::span<const std::size_t> usage, epee::span<const boost::uuids::uuid> out_map)
+ {
+ assert(usage.size() < std::numeric_limits<std::size_t>::max()); // prevented in constructor
+ if (usage.size() < out_map.size())
+ return std::numeric_limits<std::size_t>::max();
+
+ // small_vector uses stack space if `expected_max_channels < capacity()`
+ std::size_t lowest = std::numeric_limits<std::size_t>::max();
+ boost::container::small_vector<std::size_t, expected_max_channels> choices;
+ static_assert(sizeof(choices) < 256, "choices is too large based on current configuration");
+
+ for (const boost::uuids::uuid& out : out_map)
+ {
+ if (!out.is_nil())
+ {
+ const std::size_t location = std::addressof(out) - out_map.begin();
+ if (usage[location] < lowest)
+ {
+ lowest = usage[location];
+ choices = {location};
+ }
+ else if (usage[location] == lowest)
+ choices.push_back(location);
+ }
+ }
+
+ switch (choices.size())
+ {
+ case 0:
+ return std::numeric_limits<std::size_t>::max();
+ case 1:
+ return choices[0];
+ default:
+ break;
+ }
+
+ return choices[crypto::rand_idx(choices.size())];
+ }
+ } // anonymous
+
+ connection_map::connection_map(std::vector<boost::uuids::uuid> out_connections, const std::size_t stems)
+ : out_mapping_(std::move(out_connections)),
+ in_mapping_(),
+ usage_count_()
+ {
+ // max value is used by `select_stem` as error case
+ if (stems == std::numeric_limits<std::size_t>::max())
+ MONERO_THROW(common_error::kInvalidArgument, "stems value cannot be max size_t");
+
+ usage_count_.resize(stems);
+ if (stems < out_mapping_.size())
+ {
+ for (unsigned i = 0; i < stems; ++i)
+ std::swap(out_mapping_[i], out_mapping_.at(i + crypto::rand_idx(out_mapping_.size() - i)));
+
+ out_mapping_.resize(stems);
+ }
+ else
+ {
+ std::shuffle(out_mapping_.begin(), out_mapping_.end(), crypto::random_device{});
+ }
+ }
+
+ connection_map::~connection_map() noexcept
+ {}
+
+ connection_map connection_map::clone() const
+ {
+ return {*this};
+ }
+
+ bool connection_map::update(std::vector<boost::uuids::uuid> current)
+ {
+ std::sort(current.begin(), current.end());
+
+ bool replace = false;
+ for (auto& existing_out : out_mapping_)
+ {
+ const auto elem = std::lower_bound(current.begin(), current.end(), existing_out);
+ if (elem == current.end() || *elem != existing_out)
+ {
+ existing_out = boost::uuids::nil_uuid();
+ replace = true;
+ }
+ else // already using connection, remove it from candidate list
+ current.erase(elem);
+ }
+
+ if (!replace && out_mapping_.size() == usage_count_.size())
+ return false;
+
+ const std::size_t existing_outs = out_mapping_.size();
+ for (std::size_t i = 0; i < usage_count_.size() && !current.empty(); ++i)
+ {
+ const bool increase_stems = out_mapping_.size() <= i;
+ if (increase_stems || out_mapping_[i].is_nil())
+ {
+ std::swap(current.back(), current.at(crypto::rand_idx(current.size())));
+ if (increase_stems)
+ out_mapping_.push_back(current.back());
+ else
+ out_mapping_[i] = current.back();
+ current.pop_back();
+ }
+ }
+
+ return replace || existing_outs < out_mapping_.size();
+ }
+
+ std::size_t connection_map::size() const noexcept
+ {
+ std::size_t count = 0;
+ for (const boost::uuids::uuid& connection : out_mapping_)
+ {
+ if (!connection.is_nil())
+ ++count;
+ }
+ return count;
+ }
+
+ boost::uuids::uuid connection_map::get_stem(const boost::uuids::uuid& source)
+ {
+ auto elem = std::lower_bound(in_mapping_.begin(), in_mapping_.end(), source, key_less{});
+ if (elem == in_mapping_.end() || elem->first != source)
+ {
+ const std::size_t index = select_stem(epee::to_span(usage_count_), epee::to_span(out_mapping_));
+ if (out_mapping_.size() < index)
+ return boost::uuids::nil_uuid();
+
+ elem = in_mapping_.emplace(elem, source, index);
+ usage_count_[index]++;
+ }
+ else if (out_mapping_.at(elem->second).is_nil()) // stem connection disconnected after mapping
+ {
+ usage_count_.at(elem->second)--;
+ const std::size_t index = select_stem(epee::to_span(usage_count_), epee::to_span(out_mapping_));
+ if (out_mapping_.size() < index)
+ {
+ in_mapping_.erase(elem);
+ return boost::uuids::nil_uuid();
+ }
+
+ elem->second = index;
+ usage_count_[index]++;
+ }
+
+ return out_mapping_[elem->second];
+ }
+} // dandelionpp
+} // net
diff --git a/src/net/dandelionpp.h b/src/net/dandelionpp.h
new file mode 100644
index 000000000..75b63bc0c
--- /dev/null
+++ b/src/net/dandelionpp.h
@@ -0,0 +1,106 @@
+// Copyright (c) 2019, 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.
+
+#pragma once
+
+#include <boost/uuid/uuid.hpp>
+#include <cstddef>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "span.h"
+
+namespace net
+{
+namespace dandelionpp
+{
+ //! Assists with mapping source -> stem and tracking connections for stem.
+ class connection_map
+ {
+ // Make sure to update clone method if changing members
+ std::vector<boost::uuids::uuid> out_mapping_; //<! Current outgoing uuid connection at index.
+ std::vector<std::pair<boost::uuids::uuid, std::size_t>> in_mapping_; //<! uuid source to an `out_mapping_` index.
+ std::vector<std::size_t> usage_count_;
+
+ // Use clone method to prevent "hidden" copies.
+ connection_map(const connection_map&) = default;
+
+ public:
+ using value_type = boost::uuids::uuid;
+ using size_type = std::vector<boost::uuids::uuid>::size_type;
+ using difference_type = std::vector<boost::uuids::uuid>::difference_type;
+ using reference = const boost::uuids::uuid&;
+ using const_reference = reference;
+ using iterator = std::vector<boost::uuids::uuid>::const_iterator;
+ using const_iterator = iterator;
+
+ //! Initialized with zero stem connections.
+ explicit connection_map()
+ : connection_map(std::vector<boost::uuids::uuid>{}, 0)
+ {}
+
+ //! Initialized with `out_connections` and `stem_count`.
+ explicit connection_map(std::vector<boost::uuids::uuid> out_connections, std::size_t stems);
+
+ connection_map(connection_map&&) = default;
+ ~connection_map() noexcept;
+ connection_map& operator=(connection_map&&) = default;
+ connection_map& operator=(const connection_map&) = delete;
+
+ //! \return An exact duplicate of `this` map.
+ connection_map clone() const;
+
+ //! \return First stem connection.
+ const_iterator begin() const noexcept
+ {
+ return out_mapping_.begin();
+ }
+
+ //! \return One-past the last stem connection.
+ const_iterator end() const noexcept
+ {
+ return out_mapping_.end();
+ }
+
+ /*! Merges in current connections with the previous set of connections.
+ If a connection died, a new one will take its place in the stem or
+ the stem is marked as dead.
+
+ \param connections Current outbound connection ids.
+ \return True if any updates to `get_connections()` was made. */
+ bool update(std::vector<boost::uuids::uuid> current);
+
+ //! \return Number of outgoing connections in use.
+ std::size_t size() const noexcept;
+
+ //! \return Current stem mapping for `source` or `nil_uuid()` if none is possible.
+ boost::uuids::uuid get_stem(const boost::uuids::uuid& source);
+ };
+} // dandelionpp
+} // net
diff --git a/src/net/parse.cpp b/src/net/parse.cpp
index 7b74f2b75..ce580afe7 100644
--- a/src/net/parse.cpp
+++ b/src/net/parse.cpp
@@ -76,7 +76,7 @@ namespace net
return i2p_address::make(address, default_port);
boost::system::error_code ec;
- boost::asio::ip::address_v6 v6 = boost::asio::ip::make_address_v6(host_str, ec);
+ boost::asio::ip::address_v6 v6 = boost::asio::ip::address_v6::from_string(host_str, ec);
ipv6 = !ec;
std::uint16_t port = default_port;
diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp
index bb51be242..c7fc058ca 100644
--- a/src/p2p/net_node.cpp
+++ b/src/p2p/net_node.cpp
@@ -144,7 +144,7 @@ namespace nodetool
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_exclusive_node = {"add-exclusive-node", "Specify list of peers to connect to only."
" If this option is given the options add-priority-node and seed-node are ignored"};
const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_seed_node = {"seed-node", "Connect to a node to retrieve peer addresses, and disconnect"};
- const command_line::arg_descriptor<std::vector<std::string> > arg_proxy = {"proxy", "<network-type>,<socks-ip:port>[,max_connections] i.e. \"tor,127.0.0.1:9050,100\""};
+ const command_line::arg_descriptor<std::vector<std::string> > arg_proxy = {"proxy", "<network-type>,<socks-ip:port>[,max_connections][,disable_noise] i.e. \"tor,127.0.0.1:9050,100,disable_noise\""};
const command_line::arg_descriptor<std::vector<std::string> > arg_anonymous_inbound = {"anonymous-inbound", "<hidden-service-address>,<[bind-ip:]port>[,max_connections] i.e. \"x.onion,127.0.0.1:18083,100\""};
const command_line::arg_descriptor<bool> arg_p2p_hide_my_port = {"hide-my-port", "Do not announce yourself as peerlist candidate", false, true};
const command_line::arg_descriptor<bool> arg_no_sync = {"no-sync", "Don't synchronize the blockchain with other peers", false};
@@ -163,7 +163,7 @@ namespace nodetool
boost::optional<std::vector<proxy>> get_proxies(boost::program_options::variables_map const& vm)
{
- namespace ip = boost::asio::ip;
+ namespace ip = boost::asio::ip;
std::vector<proxy> proxies{};
@@ -183,14 +183,25 @@ namespace nodetool
const boost::string_ref proxy{next->begin(), next->size()};
++next;
- if (!next.eof())
+ for (unsigned count = 0; !next.eof(); ++count, ++next)
{
- proxies.back().max_connections = get_max_connections(*next);
- if (proxies.back().max_connections == 0)
+ if (2 <= count)
{
- MERROR("Invalid max connections given to --" << arg_proxy.name);
+ MERROR("Too many ',' characters given to --" << arg_proxy.name);
return boost::none;
}
+
+ if (boost::string_ref{next->begin(), next->size()} == "disable_noise")
+ proxies.back().noise = false;
+ else
+ {
+ proxies.back().max_connections = get_max_connections(*next);
+ if (proxies.back().max_connections == 0)
+ {
+ MERROR("Invalid max connections given to --" << arg_proxy.name);
+ return boost::none;
+ }
+ }
}
switch (epee::net_utils::zone_from_string(zone))
@@ -214,7 +225,7 @@ namespace nodetool
return boost::none;
}
proxies.back().address = ip::tcp::endpoint{ip::address_v4{boost::endian::native_to_big(ip)}, port};
- }
+ }
return proxies;
}
diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h
index 6d2ae878f..255a1fc1f 100644
--- a/src/p2p/net_node.h
+++ b/src/p2p/net_node.h
@@ -43,6 +43,7 @@
#include <vector>
#include "cryptonote_config.h"
+#include "cryptonote_protocol/levin_notify.h"
#include "warnings.h"
#include "net/abstract_tcp_server2.h"
#include "net/levin_protocol_handler.h"
@@ -66,12 +67,14 @@ namespace nodetool
proxy()
: max_connections(-1),
address(),
- zone(epee::net_utils::zone::invalid)
+ zone(epee::net_utils::zone::invalid),
+ noise(true)
{}
std::int64_t max_connections;
boost::asio::ip::tcp::endpoint address;
epee::net_utils::zone zone;
+ bool noise;
};
struct anonymous_inbound
@@ -154,6 +157,7 @@ namespace nodetool
m_bind_ipv6_address(),
m_port(),
m_port_ipv6(),
+ m_notifier(),
m_our_address(),
m_peerlist(),
m_config{},
@@ -172,6 +176,7 @@ namespace nodetool
m_bind_ipv6_address(),
m_port(),
m_port_ipv6(),
+ m_notifier(),
m_our_address(),
m_peerlist(),
m_config{},
@@ -189,6 +194,7 @@ namespace nodetool
std::string m_bind_ipv6_address;
std::string m_port;
std::string m_port_ipv6;
+ cryptonote::levin::notify m_notifier;
epee::net_utils::network_address m_our_address; // in anonymity networks
peerlist_manager m_peerlist;
config m_config;
@@ -255,7 +261,6 @@ namespace nodetool
size_t get_public_gray_peers_count();
void get_public_peerlist(std::vector<peerlist_entry>& gray, std::vector<peerlist_entry>& white);
void get_peerlist(std::vector<peerlist_entry>& gray, std::vector<peerlist_entry>& white);
- size_t get_zone_count() const { return m_network_zones.size(); }
void change_max_out_public_peers(size_t count);
uint32_t get_max_out_public_peers() const;
@@ -330,6 +335,7 @@ namespace nodetool
virtual void callback(p2p_connection_context& context);
//----------------- i_p2p_endpoint -------------------------------------------------------------
virtual bool relay_notify_to_list(int command, const epee::span<const uint8_t> data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections);
+ virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs);
virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context);
virtual bool invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context);
virtual bool drop_connection(const epee::net_utils::connection_context_base& context);
@@ -349,8 +355,7 @@ namespace nodetool
bool get_local_node_data(basic_node_data& node_data, const network_zone& zone);
//bool get_local_handshake_data(handshake_data& hshd);
- bool merge_peerlist_with_local(const std::vector<peerlist_entry>& bs);
- bool fix_time_delta(std::vector<peerlist_entry>& local_peerlist, time_t local_time, int64_t& delta);
+ bool sanitize_peerlist(std::vector<peerlist_entry>& local_peerlist);
bool connections_maker();
bool peer_sync_idle_maker();
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 8c0cff7e2..97a18b519 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -383,6 +383,9 @@ namespace nodetool
m_offline = command_line::get_arg(vm, cryptonote::arg_offline);
m_use_ipv6 = command_line::get_arg(vm, arg_p2p_use_ipv6);
m_require_ipv4 = command_line::get_arg(vm, arg_p2p_require_ipv4);
+ public_zone.m_notifier = cryptonote::levin::notify{
+ public_zone.m_net_server.get_io_service(), public_zone.m_net_server.get_config_shared(), nullptr
+ };
if (command_line::has_arg(vm, arg_p2p_add_peer))
{
@@ -462,6 +465,7 @@ namespace nodetool
return false;
+ epee::byte_slice noise = nullptr;
auto proxies = get_proxies(vm);
if (!proxies)
return false;
@@ -479,6 +483,20 @@ namespace nodetool
if (!set_max_out_peers(zone, proxy.max_connections))
return false;
+
+ epee::byte_slice this_noise = nullptr;
+ if (proxy.noise)
+ {
+ static_assert(sizeof(epee::levin::bucket_head2) < CRYPTONOTE_NOISE_BYTES, "noise bytes too small");
+ if (noise.empty())
+ noise = epee::levin::make_noise_notify(CRYPTONOTE_NOISE_BYTES);
+
+ this_noise = noise.clone();
+ }
+
+ zone.m_notifier = cryptonote::levin::notify{
+ zone.m_net_server.get_io_service(), zone.m_net_server.get_config_shared(), std::move(this_noise)
+ };
}
for (const auto& zone : m_network_zones)
@@ -494,6 +512,7 @@ namespace nodetool
if (!inbounds)
return false;
+ const std::size_t tx_relay_zones = m_network_zones.size();
for (auto& inbound : *inbounds)
{
network_zone& zone = add_zone(inbound.our_address.get_zone());
@@ -504,6 +523,12 @@ namespace nodetool
return false;
}
+ if (zone.m_connect == nullptr && tx_relay_zones <= 1)
+ {
+ MERROR("Listed --" << arg_anonymous_inbound.name << " without listing any --" << arg_proxy.name << ". The latter is necessary for sending origin txes over anonymity networks");
+ return false;
+ }
+
zone.m_bind_ip = std::move(inbound.local_ip);
zone.m_port = std::move(inbound.local_port);
zone.m_net_server.set_default_remote(std::move(inbound.default_remote));
@@ -1266,6 +1291,7 @@ namespace nodetool
ape.first_seen = first_seen_stamp ? first_seen_stamp : time(nullptr);
zone.m_peerlist.append_with_peer_anchor(ape);
+ zone.m_notifier.new_out_connection();
LOG_DEBUG_CC(*con, "CONNECTION HANDSHAKED OK.");
return true;
@@ -1420,7 +1446,7 @@ namespace nodetool
if (skipped == 0 || !filtered.empty())
break;
if (skipped)
- MGINFO("Skipping " << skipped << " possible peers as they share a class B with existing peers");
+ MINFO("Skipping " << skipped << " possible peers as they share a class B with existing peers");
}
if (filtered.empty())
{
@@ -1815,21 +1841,32 @@ namespace nodetool
}
//-----------------------------------------------------------------------------------
template<class t_payload_net_handler>
- bool node_server<t_payload_net_handler>::fix_time_delta(std::vector<peerlist_entry>& local_peerlist, time_t local_time, int64_t& delta)
+ bool node_server<t_payload_net_handler>::sanitize_peerlist(std::vector<peerlist_entry>& local_peerlist)
{
- //fix time delta
- time_t now = 0;
- time(&now);
- delta = now - local_time;
-
- for(peerlist_entry& be: local_peerlist)
+ for (size_t i = 0; i < local_peerlist.size(); ++i)
{
- if(be.last_seen > local_time)
+ bool ignore = false;
+ peerlist_entry &be = local_peerlist[i];
+ epee::net_utils::network_address &na = be.adr;
+ if (na.is_loopback() || na.is_local())
{
- MWARNING("FOUND FUTURE peerlist for entry " << be.adr.str() << " last_seen: " << be.last_seen << ", local_time(on remote node):" << local_time);
- return false;
+ ignore = true;
}
- be.last_seen += delta;
+ else if (be.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id())
+ {
+ const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>();
+ if (ipv4.ip() == 0)
+ ignore = true;
+ }
+ if (ignore)
+ {
+ MDEBUG("Ignoring " << be.adr.str());
+ std::swap(local_peerlist[i], local_peerlist[local_peerlist.size() - 1]);
+ local_peerlist.resize(local_peerlist.size() - 1);
+ --i;
+ continue;
+ }
+
#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED
be.pruning_seed = tools::make_pruning_seed(1 + (be.adr.as<epee::net_utils::ipv4_network_address>().ip()) % (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES), CRYPTONOTE_PRUNING_LOG_STRIPES);
#endif
@@ -1840,9 +1877,8 @@ namespace nodetool
template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::handle_remote_peerlist(const std::vector<peerlist_entry>& peerlist, time_t local_time, const epee::net_utils::connection_context_base& context)
{
- int64_t delta = 0;
std::vector<peerlist_entry> peerlist_ = peerlist;
- if(!fix_time_delta(peerlist_, local_time, delta))
+ if(!sanitize_peerlist(peerlist_))
return false;
const epee::net_utils::zone zone = context.m_remote_address.get_zone();
@@ -1855,8 +1891,8 @@ namespace nodetool
}
}
- LOG_DEBUG_CC(context, "REMOTE PEERLIST: TIME_DELTA: " << delta << ", remote peerlist size=" << peerlist_.size());
- LOG_DEBUG_CC(context, "REMOTE PEERLIST: " << print_peerlist_to_string(peerlist_));
+ LOG_DEBUG_CC(context, "REMOTE PEERLIST: remote peerlist size=" << peerlist_.size());
+ LOG_DEBUG_CC(context, "REMOTE PEERLIST: " << ENDL << print_peerlist_to_string(peerlist_));
return m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.merge_peerlist(peerlist_);
}
//-----------------------------------------------------------------------------------
@@ -1990,7 +2026,7 @@ namespace nodetool
}
if (c_id.first <= zone->first)
break;
-
+
++zone;
}
if (zone->first == c_id.first)
@@ -2000,6 +2036,61 @@ namespace nodetool
}
//-----------------------------------------------------------------------------------
template<class t_payload_net_handler>
+ epee::net_utils::zone node_server<t_payload_net_handler>::send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs)
+ {
+ namespace enet = epee::net_utils;
+
+ const auto send = [&txs, &source, pad_txs] (std::pair<const enet::zone, network_zone>& network)
+ {
+ if (network.second.m_notifier.send_txs(std::move(txs), source, (pad_txs || network.first != enet::zone::public_)))
+ return network.first;
+ return enet::zone::invalid;
+ };
+
+ if (m_network_zones.empty())
+ return enet::zone::invalid;
+
+ if (origin != enet::zone::invalid)
+ return send(*m_network_zones.begin()); // send all txs received via p2p over public network
+
+ if (m_network_zones.size() <= 2)
+ return send(*m_network_zones.rbegin()); // see static asserts below; sends over anonymity network iff enabled
+
+ /* These checks are to ensure that i2p is highest priority if multiple
+ zones are selected. Make sure to update logic if the values cannot be
+ in the same relative order. `m_network_zones` must be sorted map too. */
+ static_assert(std::is_same<std::underlying_type<enet::zone>::type, std::uint8_t>{}, "expected uint8_t zone");
+ static_assert(unsigned(enet::zone::invalid) == 0, "invalid expected to be 0");
+ static_assert(unsigned(enet::zone::public_) == 1, "public_ expected to be 1");
+ static_assert(unsigned(enet::zone::i2p) == 2, "i2p expected to be 2");
+ static_assert(unsigned(enet::zone::tor) == 3, "tor expected to be 3");
+
+ // check for anonymity networks with noise and connections
+ for (auto network = ++m_network_zones.begin(); network != m_network_zones.end(); ++network)
+ {
+ if (enet::zone::tor < network->first)
+ break; // unknown network
+
+ const auto status = network->second.m_notifier.get_status();
+ if (status.has_noise && status.connections_filled)
+ return send(*network);
+ }
+
+ // use the anonymity network with outbound support
+ for (auto network = ++m_network_zones.begin(); network != m_network_zones.end(); ++network)
+ {
+ if (enet::zone::tor < network->first)
+ break; // unknown network
+
+ if (network->second.m_connect)
+ return send(*network);
+ }
+
+ // configuration should not allow this scenario
+ return enet::zone::invalid;
+ }
+ //-----------------------------------------------------------------------------------
+ template<class t_payload_net_handler>
void node_server<t_payload_net_handler>::callback(p2p_connection_context& context)
{
m_payload_handler.on_callback(context);
@@ -2227,6 +2318,15 @@ namespace nodetool
network_zone& zone = m_network_zones.at(context.m_remote_address.get_zone());
+ // test only the remote end's zone, otherwise an attacker could connect to you on clearnet
+ // and pass in a tor connection's peer id, and deduce the two are the same if you reject it
+ if(arg.node_data.peer_id == zone.m_config.m_peer_id)
+ {
+ LOG_DEBUG_CC(context, "Connection to self detected, dropping connection");
+ drop_connection(context);
+ return 1;
+ }
+
if (zone.m_current_number_of_in_peers >= zone.m_config.m_net_config.max_in_connection_count) // in peers limit
{
LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came, but already have max incoming connections, so dropping this one.");
@@ -2253,7 +2353,7 @@ namespace nodetool
context.m_in_timedsync = false;
context.m_rpc_port = arg.node_data.rpc_port;
- if(arg.node_data.peer_id != zone.m_config.m_peer_id && arg.node_data.my_port && zone.m_can_pingback)
+ if(arg.node_data.my_port && zone.m_can_pingback)
{
peerid_type peer_id_l = arg.node_data.peer_id;
uint32_t port_l = arg.node_data.my_port;
diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h
index 34d151f5f..239814c2c 100644
--- a/src/p2p/net_node_common.h
+++ b/src/p2p/net_node_common.h
@@ -33,6 +33,8 @@
#include <boost/uuid/uuid.hpp>
#include <utility>
#include <vector>
+#include "cryptonote_basic/blobdatatype.h"
+#include "net/enums.h"
#include "net/net_utils_base.h"
#include "p2p_protocol_defs.h"
@@ -46,12 +48,12 @@ namespace nodetool
struct i_p2p_endpoint
{
virtual bool relay_notify_to_list(int command, const epee::span<const uint8_t> data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections)=0;
+ virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs)=0;
virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context)=0;
virtual bool invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context)=0;
virtual bool drop_connection(const epee::net_utils::connection_context_base& context)=0;
virtual void request_callback(const epee::net_utils::connection_context_base& context)=0;
virtual uint64_t get_public_connections_count()=0;
- virtual size_t get_zone_count() const=0;
virtual void for_each_connection(std::function<bool(t_connection_context&, peerid_type, uint32_t)> f)=0;
virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(t_connection_context&, peerid_type, uint32_t)> f)=0;
virtual bool block_host(const epee::net_utils::network_address &address, time_t seconds = 0)=0;
@@ -71,6 +73,10 @@ namespace nodetool
{
return false;
}
+ virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs)
+ {
+ return epee::net_utils::zone::invalid;
+ }
virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context)
{
return false;
@@ -96,11 +102,6 @@ namespace nodetool
return false;
}
- virtual size_t get_zone_count() const
- {
- return 0;
- }
-
virtual uint64_t get_public_connections_count()
{
return false;
diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h
index 05eb36e65..c2773981c 100644
--- a/src/p2p/net_peerlist_boost_serialization.h
+++ b/src/p2p/net_peerlist_boost_serialization.h
@@ -95,7 +95,9 @@ namespace boost
{
uint32_t ip{na.ip()};
uint16_t port{na.port()};
+ ip = SWAP32LE(ip);
a & ip;
+ ip = SWAP32LE(ip);
a & port;
if (!typename Archive::is_saving())
na = epee::net_utils::ipv4_network_address{ip, port};
diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc
index 6270d4d14..ff6fee95c 100644
--- a/src/ringct/bulletproofs.cc
+++ b/src/ringct/bulletproofs.cc
@@ -101,7 +101,10 @@ static rct::key get_exponent(const rct::key &base, size_t idx)
{
static const std::string salt("bulletproof");
std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + salt + tools::get_varint_data(idx);
- const rct::key e = rct::hashToPoint(rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size())));
+ rct::key e;
+ ge_p3 e_p3;
+ rct::hash_to_p3(e_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size())));
+ ge_p3_tobytes(e.bytes, &e_p3);
CHECK_AND_ASSERT_THROW_MES(!(e == rct::identity()), "Exponent is point at infinity");
return e;
}
diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp
index b5499262f..6e4d063df 100644
--- a/src/ringct/rctOps.cpp
+++ b/src/ringct/rctOps.cpp
@@ -620,44 +620,16 @@ namespace rct {
sc_reduce32(rv.bytes);
return rv;
}
-
- key hashToPointSimple(const key & hh) {
- key pointk;
- ge_p1p1 point2;
- ge_p2 point;
- ge_p3 res;
- key h = cn_fast_hash(hh);
- CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&res, h.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__));
- ge_p3_to_p2(&point, &res);
- ge_mul8(&point2, &point);
- ge_p1p1_to_p3(&res, &point2);
- ge_p3_tobytes(pointk.bytes, &res);
- return pointk;
- }
- key hashToPoint(const key & hh) {
- key pointk;
- ge_p2 point;
- ge_p1p1 point2;
- ge_p3 res;
- key h = cn_fast_hash(hh);
- ge_fromfe_frombytes_vartime(&point, h.bytes);
- ge_mul8(&point2, &point);
- ge_p1p1_to_p3(&res, &point2);
- ge_p3_tobytes(pointk.bytes, &res);
- return pointk;
- }
-
- void hashToPoint(key & pointk, const key & hh) {
- ge_p2 point;
- ge_p1p1 point2;
- ge_p3 res;
- key h = cn_fast_hash(hh);
- ge_fromfe_frombytes_vartime(&point, h.bytes);
- ge_mul8(&point2, &point);
- ge_p1p1_to_p3(&res, &point2);
- ge_p3_tobytes(pointk.bytes, &res);
- }
+ // Hash a key to p3 representation
+ void hash_to_p3(ge_p3 &hash8_p3, const key &k) {
+ key hash_key = cn_fast_hash(k);
+ ge_p2 hash_p2;
+ ge_fromfe_frombytes_vartime(&hash_p2, hash_key.bytes);
+ ge_p1p1 hash8_p1p1;
+ ge_mul8(&hash8_p1p1, &hash_p2);
+ ge_p1p1_to_p3(&hash8_p3, &hash8_p1p1);
+ }
//sums a vector of curve points (for scalars use sc_add)
void sumKeys(key & Csum, const keyV & Cis) {
diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h
index dd6d87593..c24d48e9a 100644
--- a/src/ringct/rctOps.h
+++ b/src/ringct/rctOps.h
@@ -172,10 +172,7 @@ namespace rct {
key cn_fast_hash(const key64 keys);
key hash_to_scalar(const key64 keys);
- //returns hashToPoint as described in https://github.com/ShenNoether/ge_fromfe_writeup
- key hashToPointSimple(const key &in);
- key hashToPoint(const key &in);
- void hashToPoint(key &out, const key &in);
+ void hash_to_p3(ge_p3 &hash8_p3, const key &k);
//sums a vector of curve points (for scalars use sc_add)
void sumKeys(key & Csum, const key &Cis);
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index ff2a81d43..a7b265d63 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -163,14 +163,11 @@ namespace rct {
return verifyBorromean(bb, P1_p3, P2_p3);
}
- //Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures)
- //This is a just slghtly more efficient version than the ones described below
- //(will be explained in more detail in Ring Multisig paper
- //These are aka MG signatutes in earlier drafts of the ring ct paper
- // c.f. https://eprint.iacr.org/2015/1098 section 2.
- // Gen creates a signature which proves that for some column in the keymatrix "pk"
- // the signer knows a secret key for each row in that column
- // Ver verifies that the MG sig was created correctly
+ // MLSAG signatures
+ // See paper by Noether (https://eprint.iacr.org/2015/1098)
+ // This generalization allows for some dimensions not to require linkability;
+ // this is used in practice for commitment data within signatures
+ // Note that using more than one linkable dimension is not recommended.
mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev) {
mgSig rv;
size_t cols = pk.size();
@@ -188,6 +185,7 @@ namespace rct {
size_t i = 0, j = 0, ii = 0;
key c, c_old, L, R, Hi;
+ ge_p3 Hi_p3;
sc_0(c_old.bytes);
vector<geDsmp> Ip(dsRows);
rv.II = keyV(dsRows);
@@ -208,7 +206,8 @@ namespace rct {
rv.II[i] = kLRki->ki;
}
else {
- Hi = hashToPoint(pk[index][i]);
+ hash_to_p3(Hi_p3, pk[index][i]);
+ ge_p3_tobytes(Hi.bytes, &Hi_p3);
hwdev.mlsag_prepare(Hi, xx[i], alpha[i] , aG[i] , aHP[i] , rv.II[i]);
toHash[3 * i + 2] = aG[i];
toHash[3 * i + 3] = aHP[i];
@@ -235,7 +234,8 @@ namespace rct {
sc_0(c.bytes);
for (j = 0; j < dsRows; j++) {
addKeys2(L, rv.ss[i][j], c_old, pk[i][j]);
- hashToPoint(Hi, pk[i][j]);
+ hash_to_p3(Hi_p3, pk[i][j]);
+ ge_p3_tobytes(Hi.bytes, &Hi_p3);
addKeys3(R, rv.ss[i][j], Hi, c_old, Ip[j].k);
toHash[3 * j + 1] = pk[i][j];
toHash[3 * j + 2] = L;
@@ -260,43 +260,42 @@ namespace rct {
return rv;
}
- //Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures)
- //This is a just slghtly more efficient version than the ones described below
- //(will be explained in more detail in Ring Multisig paper
- //These are aka MG signatutes in earlier drafts of the ring ct paper
- // c.f. https://eprint.iacr.org/2015/1098 section 2.
- // Gen creates a signature which proves that for some column in the keymatrix "pk"
- // the signer knows a secret key for each row in that column
- // Ver verifies that the MG sig was created correctly
+ // MLSAG signatures
+ // See paper by Noether (https://eprint.iacr.org/2015/1098)
+ // This generalization allows for some dimensions not to require linkability;
+ // this is used in practice for commitment data within signatures
+ // Note that using more than one linkable dimension is not recommended.
bool MLSAG_Ver(const key &message, const keyM & pk, const mgSig & rv, size_t dsRows) {
-
size_t cols = pk.size();
- CHECK_AND_ASSERT_MES(cols >= 2, false, "Error! What is c if cols = 1!");
+ CHECK_AND_ASSERT_MES(cols >= 2, false, "Signature must contain more than one public key");
size_t rows = pk[0].size();
- CHECK_AND_ASSERT_MES(rows >= 1, false, "Empty pk");
+ CHECK_AND_ASSERT_MES(rows >= 1, false, "Bad total row number");
for (size_t i = 1; i < cols; ++i) {
- CHECK_AND_ASSERT_MES(pk[i].size() == rows, false, "pk is not rectangular");
+ CHECK_AND_ASSERT_MES(pk[i].size() == rows, false, "Bad public key matrix dimensions");
}
- CHECK_AND_ASSERT_MES(rv.II.size() == dsRows, false, "Bad II size");
- CHECK_AND_ASSERT_MES(rv.ss.size() == cols, false, "Bad rv.ss size");
+ CHECK_AND_ASSERT_MES(rv.II.size() == dsRows, false, "Wrong number of key images present");
+ CHECK_AND_ASSERT_MES(rv.ss.size() == cols, false, "Bad scalar matrix dimensions");
for (size_t i = 0; i < cols; ++i) {
- CHECK_AND_ASSERT_MES(rv.ss[i].size() == rows, false, "rv.ss is not rectangular");
+ CHECK_AND_ASSERT_MES(rv.ss[i].size() == rows, false, "Bad scalar matrix dimensions");
}
- CHECK_AND_ASSERT_MES(dsRows <= rows, false, "Bad dsRows value");
+ CHECK_AND_ASSERT_MES(dsRows <= rows, false, "Non-double-spend rows cannot exceed total rows");
- for (size_t i = 0; i < rv.ss.size(); ++i)
- for (size_t j = 0; j < rv.ss[i].size(); ++j)
- CHECK_AND_ASSERT_MES(sc_check(rv.ss[i][j].bytes) == 0, false, "Bad ss slot");
- CHECK_AND_ASSERT_MES(sc_check(rv.cc.bytes) == 0, false, "Bad cc");
+ for (size_t i = 0; i < rv.ss.size(); ++i) {
+ for (size_t j = 0; j < rv.ss[i].size(); ++j) {
+ CHECK_AND_ASSERT_MES(sc_check(rv.ss[i][j].bytes) == 0, false, "Bad signature scalar");
+ }
+ }
+ CHECK_AND_ASSERT_MES(sc_check(rv.cc.bytes) == 0, false, "Bad initial signature hash");
size_t i = 0, j = 0, ii = 0;
- key c, L, R, Hi;
+ key c, L, R;
key c_old = copy(rv.cc);
vector<geDsmp> Ip(dsRows);
for (i = 0 ; i < dsRows ; i++) {
+ CHECK_AND_ASSERT_MES(!(rv.II[i] == rct::identity()), false, "Bad key image");
precomp(Ip[i].k, rv.II[i]);
}
- size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper
+ size_t ndsRows = 3 * dsRows; // number of dimensions not requiring linkability
keyV toHash(1 + 3 * dsRows + 2 * (rows - dsRows));
toHash[0] = message;
i = 0;
@@ -304,9 +303,14 @@ namespace rct {
sc_0(c.bytes);
for (j = 0; j < dsRows; j++) {
addKeys2(L, rv.ss[i][j], c_old, pk[i][j]);
- hashToPoint(Hi, pk[i][j]);
- CHECK_AND_ASSERT_MES(!(Hi == rct::identity()), false, "Data hashed to point at infinity");
- addKeys3(R, rv.ss[i][j], Hi, c_old, Ip[j].k);
+
+ // Compute R directly
+ ge_p3 hash8_p3;
+ hash_to_p3(hash8_p3, pk[i][j]);
+ ge_p2 R_p2;
+ ge_double_scalarmult_precomp_vartime(&R_p2, rv.ss[i][j].bytes, &hash8_p3, c_old.bytes, Ip[j].k);
+ ge_tobytes(R.bytes, &R_p2);
+
toHash[3 * j + 1] = pk[i][j];
toHash[3 * j + 2] = L;
toHash[3 * j + 3] = R;
@@ -317,6 +321,7 @@ namespace rct {
toHash[ndsRows + 2 * ii + 2] = L;
}
c = hash_to_scalar(toHash);
+ CHECK_AND_ASSERT_MES(!(c == rct::zero()), false, "Bad signature hash");
copy(c_old, c);
i = (i + 1);
}
diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt
index 06577d37e..d98670e05 100644
--- a/src/rpc/CMakeLists.txt
+++ b/src/rpc/CMakeLists.txt
@@ -30,6 +30,7 @@ set(rpc_base_sources
rpc_args.cpp)
set(rpc_sources
+ bootstrap_daemon.cpp
core_rpc_server.cpp
rpc_handler.cpp
instanciations)
@@ -53,6 +54,7 @@ set(daemon_rpc_server_headers)
set(rpc_daemon_private_headers
+ bootstrap_daemon.h
core_rpc_server.h
core_rpc_server_commands_defs.h
core_rpc_server_error_codes.h)
diff --git a/src/rpc/bootstrap_daemon.cpp b/src/rpc/bootstrap_daemon.cpp
new file mode 100644
index 000000000..6f8426ee7
--- /dev/null
+++ b/src/rpc/bootstrap_daemon.cpp
@@ -0,0 +1,95 @@
+#include "bootstrap_daemon.h"
+
+#include <stdexcept>
+
+#include "crypto/crypto.h"
+#include "cryptonote_core/cryptonote_core.h"
+#include "misc_log_ex.h"
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc.bootstrap_daemon"
+
+namespace cryptonote
+{
+
+ bootstrap_daemon::bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node) noexcept
+ : m_get_next_public_node(get_next_public_node)
+ {
+ }
+
+ bootstrap_daemon::bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials)
+ : bootstrap_daemon(nullptr)
+ {
+ if (!set_server(address, credentials))
+ {
+ throw std::runtime_error("invalid bootstrap daemon address or credentials");
+ }
+ }
+
+ std::string bootstrap_daemon::address() const noexcept
+ {
+ const auto& host = m_http_client.get_host();
+ if (host.empty())
+ {
+ return std::string();
+ }
+ return host + ":" + m_http_client.get_port();
+ }
+
+ boost::optional<uint64_t> bootstrap_daemon::get_height()
+ {
+ cryptonote::COMMAND_RPC_GET_HEIGHT::request req;
+ cryptonote::COMMAND_RPC_GET_HEIGHT::response res;
+
+ if (!invoke_http_json("/getheight", req, res))
+ {
+ return boost::none;
+ }
+
+ if (res.status != CORE_RPC_STATUS_OK)
+ {
+ return boost::none;
+ }
+
+ return res.height;
+ }
+
+ bool bootstrap_daemon::handle_result(bool success)
+ {
+ if (!success && m_get_next_public_node)
+ {
+ m_http_client.disconnect();
+ }
+
+ return success;
+ }
+
+ bool bootstrap_daemon::set_server(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials /* = boost::none */)
+ {
+ if (!m_http_client.set_server(address, credentials))
+ {
+ MERROR("Failed to set bootstrap daemon address " << address);
+ return false;
+ }
+
+ MINFO("Changed bootstrap daemon address to " << address);
+ return true;
+ }
+
+
+ bool bootstrap_daemon::switch_server_if_needed()
+ {
+ if (!m_get_next_public_node || m_http_client.is_connected())
+ {
+ return true;
+ }
+
+ const boost::optional<std::string> address = m_get_next_public_node();
+ if (address) {
+ return set_server(*address);
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/rpc/bootstrap_daemon.h b/src/rpc/bootstrap_daemon.h
new file mode 100644
index 000000000..130a6458d
--- /dev/null
+++ b/src/rpc/bootstrap_daemon.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <functional>
+#include <vector>
+
+#include <boost/optional/optional.hpp>
+#include <boost/utility/string_ref.hpp>
+
+#include "net/http_client.h"
+#include "storages/http_abstract_invoke.h"
+
+namespace cryptonote
+{
+
+ class bootstrap_daemon
+ {
+ public:
+ bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node) noexcept;
+ bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials);
+
+ std::string address() const noexcept;
+ boost::optional<uint64_t> get_height();
+ bool handle_result(bool success);
+
+ template <class t_request, class t_response>
+ bool invoke_http_json(const boost::string_ref uri, const t_request &out_struct, t_response &result_struct)
+ {
+ if (!switch_server_if_needed())
+ {
+ return false;
+ }
+
+ return handle_result(epee::net_utils::invoke_http_json(uri, out_struct, result_struct, m_http_client));
+ }
+
+ template <class t_request, class t_response>
+ bool invoke_http_bin(const boost::string_ref uri, const t_request &out_struct, t_response &result_struct)
+ {
+ if (!switch_server_if_needed())
+ {
+ return false;
+ }
+
+ return handle_result(epee::net_utils::invoke_http_bin(uri, out_struct, result_struct, m_http_client));
+ }
+
+ template <class t_request, class t_response>
+ bool invoke_http_json_rpc(const boost::string_ref command_name, const t_request &out_struct, t_response &result_struct)
+ {
+ if (!switch_server_if_needed())
+ {
+ return false;
+ }
+
+ return handle_result(epee::net_utils::invoke_http_json_rpc("/json_rpc", std::string(command_name.begin(), command_name.end()), out_struct, result_struct, m_http_client));
+ }
+
+ private:
+ bool set_server(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials = boost::none);
+ bool switch_server_if_needed();
+
+ private:
+ epee::net_utils::http::http_simple_client m_http_client;
+ std::function<boost::optional<std::string>()> m_get_next_public_node;
+ };
+
+}
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 73138686d..7706d2cf7 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -103,6 +103,7 @@ namespace cryptonote
)
: m_core(cr)
, m_p2p(p2p)
+ , m_was_bootstrap_ever_used(false)
{}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::set_bootstrap_daemon(const std::string &address, const std::string &username_password)
@@ -116,20 +117,59 @@ namespace cryptonote
return set_bootstrap_daemon(address, credentials);
}
//------------------------------------------------------------------------------------------------------------------------------
+ boost::optional<std::string> core_rpc_server::get_random_public_node()
+ {
+ COMMAND_RPC_GET_PUBLIC_NODES::request request;
+ COMMAND_RPC_GET_PUBLIC_NODES::response response;
+
+ request.gray = true;
+ request.white = true;
+ if (!on_get_public_nodes(request, response) || response.status != CORE_RPC_STATUS_OK)
+ {
+ return boost::none;
+ }
+
+ const auto get_random_node_address = [](const std::vector<public_node>& public_nodes) -> std::string {
+ const auto& random_node = public_nodes[crypto::rand_idx(public_nodes.size())];
+ const auto address = random_node.host + ":" + std::to_string(random_node.rpc_port);
+ return address;
+ };
+
+ if (!response.white.empty())
+ {
+ return get_random_node_address(response.white);
+ }
+
+ MDEBUG("No white public node found, checking gray peers");
+
+ if (!response.gray.empty())
+ {
+ return get_random_node_address(response.gray);
+ }
+
+ MERROR("Failed to find any suitable public node");
+
+ return boost::none;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::set_bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials)
{
boost::unique_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex);
- if (!address.empty())
+ if (address.empty())
{
- if (!m_http_client.set_server(address, credentials, epee::net_utils::ssl_support_t::e_ssl_support_autodetect))
- {
- return false;
- }
+ m_bootstrap_daemon.reset(nullptr);
+ }
+ else if (address == "auto")
+ {
+ m_bootstrap_daemon.reset(new bootstrap_daemon([this]{ return get_random_public_node(); }));
+ }
+ else
+ {
+ m_bootstrap_daemon.reset(new bootstrap_daemon(address, credentials));
}
- m_bootstrap_daemon_address = address;
- m_should_use_bootstrap_daemon = !m_bootstrap_daemon_address.empty();
+ m_should_use_bootstrap_daemon = m_bootstrap_daemon.get() != nullptr;
return true;
}
@@ -220,7 +260,10 @@ namespace cryptonote
{
{
boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex);
- res.bootstrap_daemon_address = m_bootstrap_daemon_address;
+ if (m_bootstrap_daemon.get() != nullptr)
+ {
+ res.bootstrap_daemon_address = m_bootstrap_daemon->address();
+ }
}
crypto::hash top_hash;
m_core.get_blockchain_top(res.height_without_bootstrap, top_hash);
@@ -269,7 +312,10 @@ namespace cryptonote
else
{
boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex);
- res.bootstrap_daemon_address = m_bootstrap_daemon_address;
+ if (m_bootstrap_daemon.get() != nullptr)
+ {
+ res.bootstrap_daemon_address = m_bootstrap_daemon->address();
+ }
res.was_bootstrap_ever_used = m_was_bootstrap_ever_used;
}
res.database_size = m_core.get_blockchain_storage().get_db().get_database_size();
@@ -1593,8 +1639,10 @@ namespace cryptonote
boost::upgrade_lock<boost::shared_mutex> upgrade_lock(m_bootstrap_daemon_mutex);
- if (m_bootstrap_daemon_address.empty())
+ if (m_bootstrap_daemon.get() == nullptr)
+ {
return false;
+ }
if (!m_should_use_bootstrap_daemon)
{
@@ -1610,42 +1658,38 @@ namespace cryptonote
m_bootstrap_height_check_time = current_time;
}
- uint64_t top_height;
- crypto::hash top_hash;
- m_core.get_blockchain_top(top_height, top_hash);
- ++top_height; // turn top block height into blockchain height
+ boost::optional<uint64_t> bootstrap_daemon_height = m_bootstrap_daemon->get_height();
+ if (!bootstrap_daemon_height)
+ {
+ MERROR("Failed to fetch bootstrap daemon height");
+ return false;
+ }
- // query bootstrap daemon's height
- cryptonote::COMMAND_RPC_GET_HEIGHT::request getheight_req;
- cryptonote::COMMAND_RPC_GET_HEIGHT::response getheight_res;
- bool ok = epee::net_utils::invoke_http_json("/getheight", getheight_req, getheight_res, m_http_client);
- ok = ok && getheight_res.status == CORE_RPC_STATUS_OK;
+ uint64_t target_height = m_core.get_target_blockchain_height();
+ if (*bootstrap_daemon_height < target_height)
+ {
+ MINFO("Bootstrap daemon is out of sync");
+ return m_bootstrap_daemon->handle_result(false);
+ }
- m_should_use_bootstrap_daemon = ok && top_height + 10 < getheight_res.height;
- MINFO((m_should_use_bootstrap_daemon ? "Using" : "Not using") << " the bootstrap daemon (our height: " << top_height << ", bootstrap daemon's height: " << (ok ? getheight_res.height : 0) << ")");
+ uint64_t top_height = m_core.get_current_blockchain_height();
+ m_should_use_bootstrap_daemon = top_height + 10 < *bootstrap_daemon_height;
+ MINFO((m_should_use_bootstrap_daemon ? "Using" : "Not using") << " the bootstrap daemon (our height: " << top_height << ", bootstrap daemon's height: " << *bootstrap_daemon_height << ")");
}
if (!m_should_use_bootstrap_daemon)
return false;
if (mode == invoke_http_mode::JON)
{
- r = epee::net_utils::invoke_http_json(command_name, req, res, m_http_client);
+ r = m_bootstrap_daemon->invoke_http_json(command_name, req, res);
}
else if (mode == invoke_http_mode::BIN)
{
- r = epee::net_utils::invoke_http_bin(command_name, req, res, m_http_client);
+ r = m_bootstrap_daemon->invoke_http_bin(command_name, req, res);
}
else if (mode == invoke_http_mode::JON_RPC)
{
- epee::json_rpc::request<typename COMMAND_TYPE::request> json_req = AUTO_VAL_INIT(json_req);
- epee::json_rpc::response<typename COMMAND_TYPE::response, std::string> json_resp = AUTO_VAL_INIT(json_resp);
- json_req.jsonrpc = "2.0";
- json_req.id = epee::serialization::storage_entry(0);
- json_req.method = command_name;
- json_req.params = req;
- r = net_utils::invoke_http_json("/json_rpc", json_req, json_resp, m_http_client);
- if (r)
- res = json_resp.result;
+ r = m_bootstrap_daemon->invoke_http_json_rpc(command_name, req, res);
}
else
{
@@ -2147,6 +2191,7 @@ namespace cryptonote
return r;
res.version = CORE_RPC_VERSION;
+ res.release = MONERO_VERSION_IS_RELEASE;
res.status = CORE_RPC_STATUS_OK;
return true;
}
@@ -2617,7 +2662,8 @@ namespace cryptonote
const command_line::arg_descriptor<std::string> core_rpc_server::arg_bootstrap_daemon_address = {
"bootstrap-daemon-address"
- , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced"
+ , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced.\n"
+ "Use 'auto' to enable automatic public nodes discovering and bootstrap daemon switching"
, ""
};
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index e91d4c953..379f6ed28 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -30,9 +30,12 @@
#pragma once
+#include <memory>
+
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
+#include "bootstrap_daemon.h"
#include "net/http_server_impl_base.h"
#include "net/http_client.h"
#include "core_rpc_server_commands_defs.h"
@@ -243,6 +246,7 @@ private:
//utils
uint64_t get_block_reward(const block& blk);
bool fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash);
+ boost::optional<std::string> get_random_public_node();
bool set_bootstrap_daemon(const std::string &address, const std::string &username_password);
bool set_bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials);
enum invoke_http_mode { JON, BIN, JON_RPC };
@@ -251,9 +255,8 @@ private:
core& m_core;
nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& m_p2p;
- std::string m_bootstrap_daemon_address;
- epee::net_utils::http::http_simple_client m_http_client;
boost::shared_mutex m_bootstrap_daemon_mutex;
+ std::unique_ptr<bootstrap_daemon> m_bootstrap_daemon;
bool m_should_use_bootstrap_daemon;
std::chrono::system_clock::time_point m_bootstrap_height_check_time;
bool m_was_bootstrap_ever_used;
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index aed967efb..325ac4343 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -87,7 +87,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 2
-#define CORE_RPC_VERSION_MINOR 9
+#define CORE_RPC_VERSION_MINOR 10
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
@@ -2053,11 +2053,13 @@ namespace cryptonote
{
std::string status;
uint32_t version;
+ bool release;
bool untrusted;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
KV_SERIALIZE(version)
+ KV_SERIALIZE(release)
KV_SERIALIZE(untrusted)
END_KV_SERIALIZE_MAP()
};
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index fe66e2230..e88421fb0 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -33,6 +33,7 @@
*
* \brief Source file that defines simple_wallet class.
*/
+#include <locale.h>
#include <thread>
#include <iostream>
#include <sstream>
@@ -45,6 +46,7 @@
#include <boost/regex.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include "include_base_utils.h"
+#include "console_handler.h"
#include "common/i18n.h"
#include "common/command_line.h"
#include "common/util.h"
@@ -201,11 +203,11 @@ namespace
const char* USAGE_SET_DESCRIPTION("set_description [free text note]");
const char* USAGE_SIGN("sign <filename>");
const char* USAGE_VERIFY("verify <filename> <address> <signature>");
- const char* USAGE_EXPORT_KEY_IMAGES("export_key_images <filename>");
+ const char* USAGE_EXPORT_KEY_IMAGES("export_key_images [all] <filename>");
const char* USAGE_IMPORT_KEY_IMAGES("import_key_images <filename>");
const char* USAGE_HW_KEY_IMAGES_SYNC("hw_key_images_sync");
const char* USAGE_HW_RECONNECT("hw_reconnect");
- const char* USAGE_EXPORT_OUTPUTS("export_outputs <filename>");
+ const char* USAGE_EXPORT_OUTPUTS("export_outputs [all] <filename>");
const char* USAGE_IMPORT_OUTPUTS("import_outputs <filename>");
const char* USAGE_SHOW_TRANSFER("show_transfer <txid>");
const char* USAGE_MAKE_MULTISIG("make_multisig <threshold> <string1> [<string>...]");
@@ -245,6 +247,7 @@ namespace
const char* USAGE_FREEZE("freeze <key_image>");
const char* USAGE_THAW("thaw <key_image>");
const char* USAGE_FROZEN("frozen <key_image>");
+ const char* USAGE_LOCK("lock");
const char* USAGE_NET_STATS("net_stats");
const char* USAGE_WELCOME("welcome");
const char* USAGE_VERSION("version");
@@ -252,9 +255,7 @@ namespace
std::string input_line(const std::string& prompt, bool yesno = false)
{
-#ifdef HAVE_READLINE
- rdln::suspend_readline pause_readline;
-#endif
+ PAUSE_READLINE();
std::cout << prompt;
if (yesno)
std::cout << " (Y/Yes/N/No)";
@@ -272,9 +273,7 @@ namespace
epee::wipeable_string input_secure_line(const char *prompt)
{
-#ifdef HAVE_READLINE
- rdln::suspend_readline pause_readline;
-#endif
+ PAUSE_READLINE();
auto pwd_container = tools::password_container::prompt(false, prompt, false);
if (!pwd_container)
{
@@ -290,9 +289,7 @@ namespace
boost::optional<tools::password_container> password_prompter(const char *prompt, bool verify)
{
-#ifdef HAVE_READLINE
- rdln::suspend_readline pause_readline;
-#endif
+ PAUSE_READLINE();
auto pwd_container = tools::password_container::prompt(verify, prompt);
if (!pwd_container)
{
@@ -803,6 +800,12 @@ bool simple_wallet::encrypted_seed(const std::vector<std::string> &args/* = std:
return print_seed(true);
}
+bool simple_wallet::restore_height(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ success_msg_writer() << m_wallet->get_refresh_from_block_height();
+ return true;
+}
+
bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if (m_wallet->key_on_device())
@@ -1248,7 +1251,7 @@ bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, b
}
else
{
- bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext);
+ bool r = m_wallet->save_to_file(filename, ciphertext);
if (!r)
{
fail_msg_writer() << tr("failed to save file ") << filename;
@@ -1309,7 +1312,7 @@ bool simple_wallet::import_multisig_main(const std::vector<std::string> &args, b
{
const std::string &filename = args[n];
std::string data;
- bool r = epee::file_io_utils::load_file_to_string(filename, data);
+ bool r = m_wallet->load_from_file(filename, data);
if (!r)
{
fail_msg_writer() << tr("failed to read file ") << filename;
@@ -1620,7 +1623,7 @@ bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args)
if (!filenames.empty())
filenames += ", ";
filenames += filename;
- if (!epee::file_io_utils::save_string_to_file(filename, cryptonote::tx_to_blob(ptx.tx)))
+ if (!m_wallet->save_to_file(filename, cryptonote::tx_to_blob(ptx.tx)))
{
fail_msg_writer() << tr("Failed to export multisig transaction to file ") << filename;
return true;
@@ -2135,6 +2138,13 @@ bool simple_wallet::frozen(const std::vector<std::string> &args)
return true;
}
+bool simple_wallet::lock(const std::vector<std::string> &args)
+{
+ m_locked = true;
+ check_for_inactivity_lock(true);
+ return true;
+}
+
bool simple_wallet::net_stats(const std::vector<std::string> &args)
{
message_writer() << std::to_string(m_wallet->get_bytes_sent()) + tr(" bytes sent");
@@ -2166,6 +2176,25 @@ bool simple_wallet::version(const std::vector<std::string> &args)
return true;
}
+bool simple_wallet::on_unknown_command(const std::vector<std::string> &args)
+{
+ if (args[0] == "exit" || args[0] == "q") // backward compat
+ return false;
+ fail_msg_writer() << boost::format(tr("Unknown command '%s', try 'help'")) % args.front();
+ return true;
+}
+
+bool simple_wallet::on_empty_command()
+{
+ return true;
+}
+
+bool simple_wallet::on_cancelled_command()
+{
+ check_for_inactivity_lock(false);
+ return true;
+}
+
bool simple_wallet::cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func)
{
std::vector<std::string> tx_aux;
@@ -2654,6 +2683,29 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std:
return true;
}
+bool simple_wallet::set_inactivity_lock_timeout(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+#ifdef _WIN32
+ tools::fail_msg_writer() << tr("Inactivity lock timeout disabled on Windows");
+ return true;
+#endif
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ uint32_t r;
+ if (epee::string_tools::get_xtype_from_string(r, args[1]))
+ {
+ m_wallet->inactivity_lock_timeout(r);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ }
+ else
+ {
+ tools::fail_msg_writer() << tr("Invalid number of seconds");
+ }
+ }
+ return true;
+}
+
bool simple_wallet::set_setup_background_mining(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
const auto pwd_container = get_and_verify_password();
@@ -2689,7 +2741,7 @@ bool simple_wallet::set_device_name(const std::vector<std::string> &args/* = std
return true;
}
- m_wallet->device_name(args[0]);
+ m_wallet->device_name(args[1]);
bool r = false;
try {
r = m_wallet->reconnect_device();
@@ -2706,6 +2758,35 @@ bool simple_wallet::set_device_name(const std::vector<std::string> &args/* = std
return true;
}
+bool simple_wallet::set_export_format(const std::vector<std::string> &args/* = std::vector<std::string()*/)
+{
+ if (args.size() < 2)
+ {
+ fail_msg_writer() << tr("Export format not specified");
+ return true;
+ }
+
+ if (boost::algorithm::iequals(args[1], "ascii"))
+ {
+ m_wallet->set_export_format(tools::wallet2::ExportFormat::Ascii);
+ }
+ else if (boost::algorithm::iequals(args[1], "binary"))
+ {
+ m_wallet->set_export_format(tools::wallet2::ExportFormat::Binary);
+ }
+ else
+ {
+ fail_msg_writer() << tr("Export format not recognized.");
+ return true;
+ }
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ }
+ return true;
+}
+
bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if(args.empty())
@@ -2733,83 +2814,86 @@ simple_wallet::simple_wallet()
, m_auto_refresh_refreshing(false)
, m_in_manual_refresh(false)
, m_current_subaddress_account(0)
+ , m_last_activity_time(time(NULL))
+ , m_locked(false)
+ , m_in_command(false)
{
m_cmd_binder.set_handler("start_mining",
- boost::bind(&simple_wallet::start_mining, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::start_mining, _1),
tr(USAGE_START_MINING),
tr("Start mining in the daemon (bg_mining and ignore_battery are optional booleans)."));
m_cmd_binder.set_handler("stop_mining",
- boost::bind(&simple_wallet::stop_mining, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::stop_mining, _1),
tr("Stop mining in the daemon."));
m_cmd_binder.set_handler("set_daemon",
- boost::bind(&simple_wallet::set_daemon, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::set_daemon, _1),
tr(USAGE_SET_DAEMON),
tr("Set another daemon to connect to."));
m_cmd_binder.set_handler("save_bc",
- boost::bind(&simple_wallet::save_bc, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::save_bc, _1),
tr("Save the current blockchain data."));
m_cmd_binder.set_handler("refresh",
- boost::bind(&simple_wallet::refresh, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::refresh, _1),
tr("Synchronize the transactions and balance."));
m_cmd_binder.set_handler("balance",
- boost::bind(&simple_wallet::show_balance, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::show_balance, _1),
tr(USAGE_SHOW_BALANCE),
tr("Show the wallet's balance of the currently selected account."));
m_cmd_binder.set_handler("incoming_transfers",
- boost::bind(&simple_wallet::show_incoming_transfers, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::show_incoming_transfers,_1),
tr(USAGE_INCOMING_TRANSFERS),
tr("Show the incoming transfers, all or filtered by availability and address index.\n\n"
"Output format:\n"
"Amount, Spent(\"T\"|\"F\"), \"frozen\"|\"locked\"|\"unlocked\", RingCT, Global Index, Transaction Hash, Address Index, [Public Key, Key Image] "));
m_cmd_binder.set_handler("payments",
- boost::bind(&simple_wallet::show_payments, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::show_payments,_1),
tr(USAGE_PAYMENTS),
tr("Show the payments for the given payment IDs."));
m_cmd_binder.set_handler("bc_height",
- boost::bind(&simple_wallet::show_blockchain_height, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::show_blockchain_height, _1),
tr("Show the blockchain height."));
- m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1),
+ m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::on_command, this, &simple_wallet::transfer, _1),
tr(USAGE_TRANSFER),
tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)"));
m_cmd_binder.set_handler("locked_transfer",
- boost::bind(&simple_wallet::locked_transfer, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::locked_transfer,_1),
tr(USAGE_LOCKED_TRANSFER),
tr("Transfer <amount> to <address> and lock it for <lockblocks> (max. 1000000). If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)"));
m_cmd_binder.set_handler("locked_sweep_all",
- boost::bind(&simple_wallet::locked_sweep_all, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::locked_sweep_all,_1),
tr(USAGE_LOCKED_SWEEP_ALL),
tr("Send all unlocked balance to an address and lock it for <lockblocks> (max. 1000000). If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. <priority> is the priority of the sweep. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability."));
m_cmd_binder.set_handler("sweep_unmixable",
- boost::bind(&simple_wallet::sweep_unmixable, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::sweep_unmixable, _1),
tr("Send all unmixable outputs to yourself with ring_size 1"));
m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1),
tr(USAGE_SWEEP_ALL),
tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. If the parameter \"outputs=<N>\" is specified and N > 0, wallet splits the transaction into N even outputs."));
m_cmd_binder.set_handler("sweep_below",
- boost::bind(&simple_wallet::sweep_below, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::sweep_below, _1),
tr(USAGE_SWEEP_BELOW),
tr("Send all unlocked outputs below the threshold to an address."));
m_cmd_binder.set_handler("sweep_single",
- boost::bind(&simple_wallet::sweep_single, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::sweep_single, _1),
tr(USAGE_SWEEP_SINGLE),
tr("Send a single output of the given key image to an address without change."));
m_cmd_binder.set_handler("donate",
- boost::bind(&simple_wallet::donate, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::donate, _1),
tr(USAGE_DONATE),
tr("Donate <amount> to the development team (donate.getmonero.org)."));
m_cmd_binder.set_handler("sign_transfer",
- boost::bind(&simple_wallet::sign_transfer, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::sign_transfer, _1),
tr(USAGE_SIGN_TRANSFER),
tr("Sign a transaction from a file. If the parameter \"export_raw\" is specified, transaction raw hex data suitable for the daemon RPC /sendrawtransaction is exported."));
m_cmd_binder.set_handler("submit_transfer",
- boost::bind(&simple_wallet::submit_transfer, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::submit_transfer, _1),
tr("Submit a signed transaction from a file."));
m_cmd_binder.set_handler("set_log",
- boost::bind(&simple_wallet::set_log, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::set_log, _1),
tr(USAGE_SET_LOG),
tr("Change the current log detail (level must be <0-4>)."));
m_cmd_binder.set_handler("account",
- boost::bind(&simple_wallet::account, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::account, _1),
tr(USAGE_ACCOUNT),
tr("If no arguments are specified, the wallet shows all the existing accounts along with their balances.\n"
"If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty).\n"
@@ -2819,34 +2903,37 @@ simple_wallet::simple_wallet()
"If the \"untag\" argument is specified, the tags assigned to the specified accounts <account_index_1>, <account_index_2> ..., are removed.\n"
"If the \"tag_description\" argument is specified, the tag <tag_name> is assigned an arbitrary text <description>."));
m_cmd_binder.set_handler("address",
- boost::bind(&simple_wallet::print_address, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::print_address, _1),
tr(USAGE_ADDRESS),
tr("If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the wallet shows all the existing addresses in the currently selected account. If \"new \" is specified, the wallet creates a new address with the provided label text (which can be empty). If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text."));
m_cmd_binder.set_handler("integrated_address",
- boost::bind(&simple_wallet::print_integrated_address, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::print_integrated_address, _1),
tr(USAGE_INTEGRATED_ADDRESS),
tr("Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID"));
m_cmd_binder.set_handler("address_book",
- boost::bind(&simple_wallet::address_book, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::address_book,_1),
tr(USAGE_ADDRESS_BOOK),
tr("Print all entries in the address book, optionally adding/deleting an entry to/from it."));
m_cmd_binder.set_handler("save",
- boost::bind(&simple_wallet::save, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::save, _1),
tr("Save the wallet data."));
m_cmd_binder.set_handler("save_watch_only",
- boost::bind(&simple_wallet::save_watch_only, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::save_watch_only, _1),
tr("Save a watch-only keys file."));
m_cmd_binder.set_handler("viewkey",
- boost::bind(&simple_wallet::viewkey, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::viewkey, _1),
tr("Display the private view key."));
m_cmd_binder.set_handler("spendkey",
- boost::bind(&simple_wallet::spendkey, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::spendkey, _1),
tr("Display the private spend key."));
m_cmd_binder.set_handler("seed",
- boost::bind(&simple_wallet::seed, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::seed, _1),
tr("Display the Electrum-style mnemonic seed"));
+ m_cmd_binder.set_handler("restore_height",
+ boost::bind(&simple_wallet::restore_height, this, _1),
+ tr("Display the restore height"));
m_cmd_binder.set_handler("set",
- boost::bind(&simple_wallet::set_variable, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::set_variable, _1),
tr(USAGE_SET_VARIABLE),
tr("Available options:\n "
"seed language\n "
@@ -2881,6 +2968,8 @@ simple_wallet::simple_wallet()
" Whether to warn if there is transaction backlog.\n "
"confirm-backlog-threshold [n]\n "
" Set a threshold for confirm-backlog to only warn if the transaction backlog is greater than n blocks.\n "
+ "confirm-export-overwrite <1|0>\n "
+ " Whether to warn if the file to be exported already exists.\n "
"refresh-from-block-height [n]\n "
" Set the height before which to ignore blocks.\n "
"auto-low-priority <1|0>\n "
@@ -2888,58 +2977,68 @@ simple_wallet::simple_wallet()
"segregate-pre-fork-outputs <1|0>\n "
" Set this if you intend to spend outputs on both Monero AND a key reusing fork.\n "
"key-reuse-mitigation2 <1|0>\n "
- " Set this if you are not sure whether you will spend on a key reusing Monero fork later.\n"
+ " Set this if you are not sure whether you will spend on a key reusing Monero fork later.\n "
"subaddress-lookahead <major>:<minor>\n "
" Set the lookahead sizes for the subaddress hash table.\n "
" Set this if you are not sure whether you will spend on a key reusing Monero fork later.\n "
"segregation-height <n>\n "
- " Set to the height of a key reusing fork you want to use, 0 to use default."));
+ " Set to the height of a key reusing fork you want to use, 0 to use default.\n "
+ "ignore-fractional-outputs <1|0>\n "
+ " Whether to ignore fractional outputs that result in net loss when spending due to fee.\n "
+ "track-uses <1|0>\n "
+ " Whether to keep track of owned outputs uses.\n "
+ "setup-background-mining <1|0>\n "
+ " Whether to enable background mining. Set this to support the network and to get a chance to receive new monero.\n "
+ "device-name <device_name[:device_spec]>\n "
+ " Device name for hardware wallet.\n "
+ "export-format <\"binary\"|\"ascii\">\n "
+ " Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n "));
m_cmd_binder.set_handler("encrypted_seed",
- boost::bind(&simple_wallet::encrypted_seed, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::encrypted_seed, _1),
tr("Display the encrypted Electrum-style mnemonic seed."));
m_cmd_binder.set_handler("rescan_spent",
- boost::bind(&simple_wallet::rescan_spent, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::rescan_spent, _1),
tr("Rescan the blockchain for spent outputs."));
m_cmd_binder.set_handler("get_tx_key",
- boost::bind(&simple_wallet::get_tx_key, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::get_tx_key, _1),
tr(USAGE_GET_TX_KEY),
tr("Get the transaction key (r) for a given <txid>."));
m_cmd_binder.set_handler("set_tx_key",
- boost::bind(&simple_wallet::set_tx_key, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::set_tx_key, _1),
tr(USAGE_SET_TX_KEY),
tr("Set the transaction key (r) for a given <txid> in case the tx was made by some other device or 3rd party wallet."));
m_cmd_binder.set_handler("check_tx_key",
- boost::bind(&simple_wallet::check_tx_key, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::check_tx_key, _1),
tr(USAGE_CHECK_TX_KEY),
tr("Check the amount going to <address> in <txid>."));
m_cmd_binder.set_handler("get_tx_proof",
- boost::bind(&simple_wallet::get_tx_proof, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::get_tx_proof, _1),
tr(USAGE_GET_TX_PROOF),
tr("Generate a signature proving funds sent to <address> in <txid>, optionally with a challenge string <message>, using either the transaction secret key (when <address> is not your wallet's address) or the view secret key (otherwise), which does not disclose the secret key."));
m_cmd_binder.set_handler("check_tx_proof",
- boost::bind(&simple_wallet::check_tx_proof, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::check_tx_proof, _1),
tr(USAGE_CHECK_TX_PROOF),
tr("Check the proof for funds going to <address> in <txid> with the challenge string <message> if any."));
m_cmd_binder.set_handler("get_spend_proof",
- boost::bind(&simple_wallet::get_spend_proof, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::get_spend_proof, _1),
tr(USAGE_GET_SPEND_PROOF),
tr("Generate a signature proving that you generated <txid> using the spend secret key, optionally with a challenge string <message>."));
m_cmd_binder.set_handler("check_spend_proof",
- boost::bind(&simple_wallet::check_spend_proof, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::check_spend_proof, _1),
tr(USAGE_CHECK_SPEND_PROOF),
tr("Check a signature proving that the signer generated <txid>, optionally with a challenge string <message>."));
m_cmd_binder.set_handler("get_reserve_proof",
- boost::bind(&simple_wallet::get_reserve_proof, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::get_reserve_proof, _1),
tr(USAGE_GET_RESERVE_PROOF),
tr("Generate a signature proving that you own at least this much, optionally with a challenge string <message>.\n"
"If 'all' is specified, you prove the entire sum of all of your existing accounts' balances.\n"
"Otherwise, you prove the reserve of the smallest possible amount above <amount> available in your current account."));
m_cmd_binder.set_handler("check_reserve_proof",
- boost::bind(&simple_wallet::check_reserve_proof, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::check_reserve_proof, _1),
tr(USAGE_CHECK_RESERVE_PROOF),
tr("Check a signature proving that the owner of <address> holds at least this much, optionally with a challenge string <message>."));
m_cmd_binder.set_handler("show_transfers",
- boost::bind(&simple_wallet::show_transfers, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::show_transfers, _1),
tr(USAGE_SHOW_TRANSFERS),
// Seemingly broken formatting to compensate for the backslash before the quotes.
tr("Show the incoming/outgoing transfers within an optional height range.\n\n"
@@ -2951,120 +3050,120 @@ simple_wallet::simple_wallet()
"* Excluding change and fee.\n"
"** Set of address indices used as inputs in this transfer."));
m_cmd_binder.set_handler("export_transfers",
- boost::bind(&simple_wallet::export_transfers, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::export_transfers, _1),
tr("export_transfers [in|out|all|pending|failed|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] [output=<filepath>]"),
tr("Export to CSV the incoming/outgoing transfers within an optional height range."));
m_cmd_binder.set_handler("unspent_outputs",
- boost::bind(&simple_wallet::unspent_outputs, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::unspent_outputs, _1),
tr(USAGE_UNSPENT_OUTPUTS),
tr("Show the unspent outputs of a specified address within an optional amount range."));
m_cmd_binder.set_handler("rescan_bc",
- boost::bind(&simple_wallet::rescan_blockchain, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::rescan_blockchain, _1),
tr(USAGE_RESCAN_BC),
tr("Rescan the blockchain from scratch. If \"hard\" is specified, you will lose any information which can not be recovered from the blockchain itself."));
m_cmd_binder.set_handler("set_tx_note",
- boost::bind(&simple_wallet::set_tx_note, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::set_tx_note, _1),
tr(USAGE_SET_TX_NOTE),
tr("Set an arbitrary string note for a <txid>."));
m_cmd_binder.set_handler("get_tx_note",
- boost::bind(&simple_wallet::get_tx_note, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::get_tx_note, _1),
tr(USAGE_GET_TX_NOTE),
tr("Get a string note for a txid."));
m_cmd_binder.set_handler("set_description",
- boost::bind(&simple_wallet::set_description, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::set_description, _1),
tr(USAGE_SET_DESCRIPTION),
tr("Set an arbitrary description for the wallet."));
m_cmd_binder.set_handler("get_description",
- boost::bind(&simple_wallet::get_description, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::get_description, _1),
tr(USAGE_GET_DESCRIPTION),
tr("Get the description of the wallet."));
m_cmd_binder.set_handler("status",
- boost::bind(&simple_wallet::status, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::status, _1),
tr("Show the wallet's status."));
m_cmd_binder.set_handler("wallet_info",
- boost::bind(&simple_wallet::wallet_info, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::wallet_info, _1),
tr("Show the wallet's information."));
m_cmd_binder.set_handler("sign",
- boost::bind(&simple_wallet::sign, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::sign, _1),
tr(USAGE_SIGN),
tr("Sign the contents of a file."));
m_cmd_binder.set_handler("verify",
- boost::bind(&simple_wallet::verify, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::verify, _1),
tr(USAGE_VERIFY),
tr("Verify a signature on the contents of a file."));
m_cmd_binder.set_handler("export_key_images",
- boost::bind(&simple_wallet::export_key_images, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::export_key_images, _1),
tr(USAGE_EXPORT_KEY_IMAGES),
tr("Export a signed set of key images to a <filename>."));
m_cmd_binder.set_handler("import_key_images",
- boost::bind(&simple_wallet::import_key_images, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::import_key_images, _1),
tr(USAGE_IMPORT_KEY_IMAGES),
tr("Import a signed key images list and verify their spent status."));
m_cmd_binder.set_handler("hw_key_images_sync",
- boost::bind(&simple_wallet::hw_key_images_sync, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::hw_key_images_sync, _1),
tr(USAGE_HW_KEY_IMAGES_SYNC),
tr("Synchronizes key images with the hw wallet."));
m_cmd_binder.set_handler("hw_reconnect",
- boost::bind(&simple_wallet::hw_reconnect, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::hw_reconnect, _1),
tr(USAGE_HW_RECONNECT),
tr("Attempts to reconnect HW wallet."));
m_cmd_binder.set_handler("export_outputs",
- boost::bind(&simple_wallet::export_outputs, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::export_outputs, _1),
tr(USAGE_EXPORT_OUTPUTS),
tr("Export a set of outputs owned by this wallet."));
m_cmd_binder.set_handler("import_outputs",
- boost::bind(&simple_wallet::import_outputs, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::import_outputs, _1),
tr(USAGE_IMPORT_OUTPUTS),
tr("Import a set of outputs owned by this wallet."));
m_cmd_binder.set_handler("show_transfer",
- boost::bind(&simple_wallet::show_transfer, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::show_transfer, _1),
tr(USAGE_SHOW_TRANSFER),
tr("Show information about a transfer to/from this address."));
m_cmd_binder.set_handler("password",
- boost::bind(&simple_wallet::change_password, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::change_password, _1),
tr("Change the wallet's password."));
m_cmd_binder.set_handler("payment_id",
- boost::bind(&simple_wallet::payment_id, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::payment_id, _1),
tr(USAGE_PAYMENT_ID),
tr("Generate a new random full size payment id (obsolete). These will be unencrypted on the blockchain, see integrated_address for encrypted short payment ids."));
m_cmd_binder.set_handler("fee",
- boost::bind(&simple_wallet::print_fee_info, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::print_fee_info, _1),
tr("Print the information about the current fee and transaction backlog."));
- m_cmd_binder.set_handler("prepare_multisig", boost::bind(&simple_wallet::prepare_multisig, this, _1),
+ m_cmd_binder.set_handler("prepare_multisig", boost::bind(&simple_wallet::on_command, this, &simple_wallet::prepare_multisig, _1),
tr("Export data needed to create a multisig wallet"));
- m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1),
+ m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::on_command, this, &simple_wallet::make_multisig, _1),
tr(USAGE_MAKE_MULTISIG),
tr("Turn this wallet into a multisig wallet"));
m_cmd_binder.set_handler("finalize_multisig",
- boost::bind(&simple_wallet::finalize_multisig, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::finalize_multisig, _1),
tr(USAGE_FINALIZE_MULTISIG),
tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets"));
m_cmd_binder.set_handler("exchange_multisig_keys",
- boost::bind(&simple_wallet::exchange_multisig_keys, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::exchange_multisig_keys, _1),
tr(USAGE_EXCHANGE_MULTISIG_KEYS),
tr("Performs extra multisig keys exchange rounds. Needed for arbitrary M/N multisig wallets"));
m_cmd_binder.set_handler("export_multisig_info",
- boost::bind(&simple_wallet::export_multisig, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::export_multisig, _1),
tr(USAGE_EXPORT_MULTISIG_INFO),
tr("Export multisig info for other participants"));
m_cmd_binder.set_handler("import_multisig_info",
- boost::bind(&simple_wallet::import_multisig, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::import_multisig, _1),
tr(USAGE_IMPORT_MULTISIG_INFO),
tr("Import multisig info from other participants"));
m_cmd_binder.set_handler("sign_multisig",
- boost::bind(&simple_wallet::sign_multisig, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::sign_multisig, _1),
tr(USAGE_SIGN_MULTISIG),
tr("Sign a multisig transaction from a file"));
m_cmd_binder.set_handler("submit_multisig",
- boost::bind(&simple_wallet::submit_multisig, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::submit_multisig, _1),
tr(USAGE_SUBMIT_MULTISIG),
tr("Submit a signed multisig transaction from a file"));
m_cmd_binder.set_handler("export_raw_multisig_tx",
- boost::bind(&simple_wallet::export_raw_multisig, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::export_raw_multisig, _1),
tr(USAGE_EXPORT_RAW_MULTISIG_TX),
tr("Export a signed multisig transaction to a file"));
m_cmd_binder.set_handler("mms",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS),
tr("Interface with the MMS (Multisig Messaging System)\n"
"<subcommand> is one of:\n"
@@ -3072,138 +3171,145 @@ simple_wallet::simple_wallet()
" send_signer_config, start_auto_config, stop_auto_config, auto_config\n"
"Get help about a subcommand with: help mms <subcommand>, or mms help <subcommand>"));
m_cmd_binder.set_handler("mms init",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_INIT),
tr("Initialize and configure the MMS for M/N = number of required signers/number of authorized signers multisig"));
m_cmd_binder.set_handler("mms info",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_INFO),
tr("Display current MMS configuration"));
m_cmd_binder.set_handler("mms signer",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_SIGNER),
tr("Set or modify authorized signer info (single-word label, transport address, Monero address), or list all signers"));
m_cmd_binder.set_handler("mms list",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_LIST),
tr("List all messages"));
m_cmd_binder.set_handler("mms next",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_NEXT),
tr("Evaluate the next possible multisig-related action(s) according to wallet state, and execute or offer for choice\n"
"By using 'sync' processing of waiting messages with multisig sync info can be forced regardless of wallet state"));
m_cmd_binder.set_handler("mms sync",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_SYNC),
tr("Force generation of multisig sync info regardless of wallet state, to recover from special situations like \"stale data\" errors"));
m_cmd_binder.set_handler("mms transfer",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_TRANSFER),
tr("Initiate transfer with MMS support; arguments identical to normal 'transfer' command arguments, for info see there"));
m_cmd_binder.set_handler("mms delete",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_DELETE),
tr("Delete a single message by giving its id, or delete all messages by using 'all'"));
m_cmd_binder.set_handler("mms send",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_SEND),
tr("Send a single message by giving its id, or send all waiting messages"));
m_cmd_binder.set_handler("mms receive",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_RECEIVE),
tr("Check right away for new messages to receive"));
m_cmd_binder.set_handler("mms export",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_EXPORT),
tr("Write the content of a message to a file \"mms_message_content\""));
m_cmd_binder.set_handler("mms note",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_NOTE),
tr("Send a one-line message to an authorized signer, identified by its label, or show any waiting unread notes"));
m_cmd_binder.set_handler("mms show",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_SHOW),
tr("Show detailed info about a single message"));
m_cmd_binder.set_handler("mms set",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_SET),
tr("Available options:\n "
"auto-send <1|0>\n "
" Whether to automatically send newly generated messages right away.\n "));
- m_cmd_binder.set_handler("mms send_message_config",
+ m_cmd_binder.set_handler("mms send_signer_config",
boost::bind(&simple_wallet::mms, this, _1),
tr(USAGE_MMS_SEND_SIGNER_CONFIG),
tr("Send completed signer config to all other authorized signers"));
m_cmd_binder.set_handler("mms start_auto_config",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_START_AUTO_CONFIG),
tr("Start auto-config at the auto-config manager's wallet by issuing auto-config tokens and optionally set others' labels"));
m_cmd_binder.set_handler("mms stop_auto_config",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_STOP_AUTO_CONFIG),
tr("Delete any auto-config tokens and abort a auto-config process"));
m_cmd_binder.set_handler("mms auto_config",
- boost::bind(&simple_wallet::mms, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1),
tr(USAGE_MMS_AUTO_CONFIG),
tr("Start auto-config by using the token received from the auto-config manager"));
m_cmd_binder.set_handler("print_ring",
- boost::bind(&simple_wallet::print_ring, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::print_ring, _1),
tr(USAGE_PRINT_RING),
tr("Print the ring(s) used to spend a given key image or transaction (if the ring size is > 1)\n\n"
"Output format:\n"
"Key Image, \"absolute\", list of rings"));
m_cmd_binder.set_handler("set_ring",
- boost::bind(&simple_wallet::set_ring, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::set_ring, _1),
tr(USAGE_SET_RING),
tr("Set the ring used for a given key image, so it can be reused in a fork"));
m_cmd_binder.set_handler("unset_ring",
- boost::bind(&simple_wallet::unset_ring, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::unset_ring, _1),
tr(USAGE_UNSET_RING),
tr("Unsets the ring used for a given key image or transaction"));
m_cmd_binder.set_handler("save_known_rings",
- boost::bind(&simple_wallet::save_known_rings, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::save_known_rings, _1),
tr(USAGE_SAVE_KNOWN_RINGS),
tr("Save known rings to the shared rings database"));
m_cmd_binder.set_handler("mark_output_spent",
- boost::bind(&simple_wallet::blackball, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::blackball, _1),
tr(USAGE_MARK_OUTPUT_SPENT),
tr("Mark output(s) as spent so they never get selected as fake outputs in a ring"));
m_cmd_binder.set_handler("mark_output_unspent",
- boost::bind(&simple_wallet::unblackball, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::unblackball, _1),
tr(USAGE_MARK_OUTPUT_UNSPENT),
tr("Marks an output as unspent so it may get selected as a fake output in a ring"));
m_cmd_binder.set_handler("is_output_spent",
- boost::bind(&simple_wallet::blackballed, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::blackballed, _1),
tr(USAGE_IS_OUTPUT_SPENT),
tr("Checks whether an output is marked as spent"));
m_cmd_binder.set_handler("freeze",
- boost::bind(&simple_wallet::freeze, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::freeze, _1),
tr(USAGE_FREEZE),
tr("Freeze a single output by key image so it will not be used"));
m_cmd_binder.set_handler("thaw",
- boost::bind(&simple_wallet::thaw, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::thaw, _1),
tr(USAGE_THAW),
tr("Thaw a single output by key image so it may be used again"));
m_cmd_binder.set_handler("frozen",
- boost::bind(&simple_wallet::frozen, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::frozen, _1),
tr(USAGE_FROZEN),
tr("Checks whether a given output is currently frozen by key image"));
+ m_cmd_binder.set_handler("lock",
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::lock, _1),
+ tr(USAGE_LOCK),
+ tr("Lock the wallet console, requiring the wallet password to continue"));
m_cmd_binder.set_handler("net_stats",
- boost::bind(&simple_wallet::net_stats, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::net_stats, _1),
tr(USAGE_NET_STATS),
tr("Prints simple network stats"));
m_cmd_binder.set_handler("welcome",
- boost::bind(&simple_wallet::welcome, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::welcome, _1),
tr(USAGE_WELCOME),
tr("Prints basic info about Monero for first time users"));
m_cmd_binder.set_handler("version",
- boost::bind(&simple_wallet::version, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::version, _1),
tr(USAGE_VERSION),
tr("Returns version information"));
m_cmd_binder.set_handler("help",
- boost::bind(&simple_wallet::help, this, _1),
+ boost::bind(&simple_wallet::on_command, this, &simple_wallet::help, _1),
tr(USAGE_HELP),
tr("Show the help section or the documentation about a <command>."));
+ m_cmd_binder.set_unknown_command_handler(boost::bind(&simple_wallet::on_command, this, &simple_wallet::on_unknown_command, _1));
+ m_cmd_binder.set_empty_command_handler(boost::bind(&simple_wallet::on_empty_command, this));
+ m_cmd_binder.set_cancel_handler(boost::bind(&simple_wallet::on_cancelled_command, this));
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::set_variable(const std::vector<std::string> &args)
@@ -3257,8 +3363,14 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
success_msg_writer() << "segregation-height = " << m_wallet->segregation_height();
success_msg_writer() << "ignore-fractional-outputs = " << m_wallet->ignore_fractional_outputs();
success_msg_writer() << "track-uses = " << m_wallet->track_uses();
- success_msg_writer() << "setup-background-mining = " << setup_background_mining_string + tr(" (set this to support the network and to get a chance to receive new monero)");
- success_msg_writer() << "device_name = " << m_wallet->device_name();
+ success_msg_writer() << "setup-background-mining = " << setup_background_mining_string;
+ success_msg_writer() << "device-name = " << m_wallet->device_name();
+ success_msg_writer() << "export-format = " << (m_wallet->export_format() == tools::wallet2::ExportFormat::Ascii ? "ascii" : "binary");
+ success_msg_writer() << "inactivity-lock-timeout = " << m_wallet->inactivity_lock_timeout()
+#ifdef _WIN32
+ << " (disabled on Windows)"
+#endif
+ ;
return true;
}
else
@@ -3315,8 +3427,10 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
CHECK_SIMPLE_VARIABLE("segregation-height", set_segregation_height, tr("unsigned integer"));
CHECK_SIMPLE_VARIABLE("ignore-fractional-outputs", set_ignore_fractional_outputs, tr("0 or 1"));
CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1"));
+ CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)"));
CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no"));
CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>"));
+ CHECK_SIMPLE_VARIABLE("export-format", set_export_format, tr("\"binary\" or \"ascii\""));
}
fail_msg_writer() << tr("set: unrecognized argument(s)");
return true;
@@ -4119,6 +4233,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
tr("It is recommended that you do not use them, and ask recipients who ask for one to not endanger your privacy.");
}
+ m_last_activity_time = time(NULL);
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -4957,12 +5072,16 @@ bool simple_wallet::save_bc(const std::vector<std::string>& args)
//----------------------------------------------------------------------------------------------------
void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block)
{
+ if (m_locked)
+ return;
if (!m_auto_refresh_refreshing)
m_refresh_progress_reporter.update(height, false);
}
//----------------------------------------------------------------------------------------------------
void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, uint64_t unlock_time)
{
+ if (m_locked)
+ return;
message_writer(console_color_green, false) << "\r" <<
tr("Height ") << height << ", " <<
tr("txid ") << txid << ", " <<
@@ -4993,11 +5112,15 @@ void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid,
//----------------------------------------------------------------------------------------------------
void simple_wallet::on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index)
{
+ if (m_locked)
+ return;
// Not implemented in CLI wallet
}
//----------------------------------------------------------------------------------------------------
void simple_wallet::on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index)
{
+ if (m_locked)
+ return;
message_writer(console_color_magenta, false) << "\r" <<
tr("Height ") << height << ", " <<
tr("txid ") << txid << ", " <<
@@ -5011,10 +5134,14 @@ void simple_wallet::on_money_spent(uint64_t height, const crypto::hash &txid, co
//----------------------------------------------------------------------------------------------------
void simple_wallet::on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx)
{
+ if (m_locked)
+ return;
}
//----------------------------------------------------------------------------------------------------
boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char *reason)
{
+ if (m_locked)
+ return boost::none;
// can't ask for password from a background thread
if (!m_in_manual_refresh.load(std::memory_order_relaxed))
{
@@ -5023,9 +5150,7 @@ boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char
return boost::none;
}
-#ifdef HAVE_READLINE
- rdln::suspend_readline pause_readline;
-#endif
+ PAUSE_READLINE();
std::string msg = tr("Enter password");
if (reason && *reason)
msg += std::string(" (") + reason + ")";
@@ -5046,9 +5171,7 @@ void simple_wallet::on_device_button_request(uint64_t code)
//----------------------------------------------------------------------------------------------------
boost::optional<epee::wipeable_string> simple_wallet::on_device_pin_request()
{
-#ifdef HAVE_READLINE
- rdln::suspend_readline pause_readline;
-#endif
+ PAUSE_READLINE();
std::string msg = tr("Enter device PIN");
auto pwd_container = tools::password_container::prompt(false, msg.c_str());
THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device PIN"));
@@ -5062,9 +5185,7 @@ boost::optional<epee::wipeable_string> simple_wallet::on_device_passphrase_reque
return boost::none;
}
-#ifdef HAVE_READLINE
- rdln::suspend_readline pause_readline;
-#endif
+ PAUSE_READLINE();
std::string msg = tr("Enter device passphrase");
auto pwd_container = tools::password_container::prompt(false, msg.c_str());
THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device passphrase"));
@@ -5111,9 +5232,7 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo
m_wallet->rescan_blockchain(reset == ResetHard, false, reset == ResetSoftKeepKI);
}
-#ifdef HAVE_READLINE
- rdln::suspend_readline pause_readline;
-#endif
+ PAUSE_READLINE();
message_writer() << tr("Starting refresh...");
@@ -5671,6 +5790,67 @@ bool simple_wallet::prompt_if_old(const std::vector<tools::wallet2::pending_tx>
}
return true;
}
+void simple_wallet::check_for_inactivity_lock(bool user)
+{
+ if (m_locked)
+ {
+#ifdef HAVE_READLINE
+ PAUSE_READLINE();
+ rdln::clear_screen();
+#endif
+ tools::clear_screen();
+ m_in_command = true;
+ if (!user)
+ {
+ const std::string speech = tr("I locked your Monero wallet to protect you while you were away");
+ std::vector<std::pair<std::string, size_t>> lines = tools::split_string_by_width(speech, 45);
+
+ size_t max_len = 0;
+ for (const auto &i: lines)
+ max_len = std::max(max_len, i.second);
+ const size_t n_u = max_len + 2;
+ tools::msg_writer() << " " << std::string(n_u, '_');
+ for (size_t i = 0; i < lines.size(); ++i)
+ tools::msg_writer() << (i == 0 ? "/" : i == lines.size() - 1 ? "\\" : "|") << " " << lines[i].first << std::string(max_len - lines[i].second, ' ') << " " << (i == 0 ? "\\" : i == lines.size() - 1 ? "/" : "|");
+ tools::msg_writer() << " " << std::string(n_u, '-') << std::endl <<
+ " \\ (__)" << std::endl <<
+ " \\ (oo)\\_______" << std::endl <<
+ " (__)\\ )\\/\\" << std::endl <<
+ " ||----w |" << std::endl <<
+ " || ||" << std::endl <<
+ "" << std::endl;
+ }
+ while (1)
+ {
+ tools::msg_writer() << tr("Locked due to inactivity. The wallet password is required to unlock the console.");
+ try
+ {
+ if (get_and_verify_password())
+ break;
+ }
+ catch (...) { /* do nothing, just let the loop loop */ }
+ }
+ m_last_activity_time = time(NULL);
+ m_in_command = false;
+ m_locked = false;
+ }
+}
+//----------------------------------------------------------------------------------------------------
+bool simple_wallet::on_command(bool (simple_wallet::*cmd)(const std::vector<std::string>&), const std::vector<std::string> &args)
+{
+ const time_t now = time(NULL);
+ time_t dt = now - m_last_activity_time;
+ m_last_activity_time = time(NULL);
+
+ m_in_command = true;
+ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){
+ m_last_activity_time = time(NULL);
+ m_in_command = false;
+ });
+
+ check_for_inactivity_lock(false);
+ return (this->*cmd)(args);
+}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_, bool called_by_mms)
{
@@ -7308,7 +7488,7 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
{
std::string sig_str = m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, args.size() == 3 ? args[2] : "");
const std::string filename = "monero_tx_proof";
- if (epee::file_io_utils::save_string_to_file(filename, sig_str))
+ if (m_wallet->save_to_file(filename, sig_str, true))
success_msg_writer() << tr("signature file saved to: ") << filename;
else
fail_msg_writer() << tr("failed to save signature file");
@@ -7436,7 +7616,7 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args)
// read signature file
std::string sig_str;
- if (!epee::file_io_utils::load_file_to_string(args[2], sig_str))
+ if (!m_wallet->load_from_file(args[2], sig_str))
{
fail_msg_writer() << tr("failed to load signature file");
return true;
@@ -7520,7 +7700,7 @@ bool simple_wallet::get_spend_proof(const std::vector<std::string> &args)
{
const std::string sig_str = m_wallet->get_spend_proof(txid, args.size() == 2 ? args[1] : "");
const std::string filename = "monero_spend_proof";
- if (epee::file_io_utils::save_string_to_file(filename, sig_str))
+ if (m_wallet->save_to_file(filename, sig_str, true))
success_msg_writer() << tr("signature file saved to: ") << filename;
else
fail_msg_writer() << tr("failed to save signature file");
@@ -7550,7 +7730,7 @@ bool simple_wallet::check_spend_proof(const std::vector<std::string> &args)
return true;
std::string sig_str;
- if (!epee::file_io_utils::load_file_to_string(args[1], sig_str))
+ if (!m_wallet->load_from_file(args[1], sig_str))
{
fail_msg_writer() << tr("failed to load signature file");
return true;
@@ -7609,7 +7789,7 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args)
{
const std::string sig_str = m_wallet->get_reserve_proof(account_minreserve, args.size() == 2 ? args[1] : "");
const std::string filename = "monero_reserve_proof";
- if (epee::file_io_utils::save_string_to_file(filename, sig_str))
+ if (m_wallet->save_to_file(filename, sig_str, true))
success_msg_writer() << tr("signature file saved to: ") << filename;
else
fail_msg_writer() << tr("failed to save signature file");
@@ -7644,7 +7824,7 @@ bool simple_wallet::check_reserve_proof(const std::vector<std::string> &args)
}
std::string sig_str;
- if (!epee::file_io_utils::load_file_to_string(args[1], sig_str))
+ if (!m_wallet->load_from_file(args[1], sig_str))
{
fail_msg_writer() << tr("failed to load signature file");
return true;
@@ -7826,7 +8006,7 @@ bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vec
uint64_t fee = pd.m_amount_in - pd.m_amount_out;
std::vector<std::pair<std::string, uint64_t>> destinations;
for (const auto &d: pd.m_dests) {
- destinations.push_back({get_account_address_as_str(m_wallet->nettype(), d.is_subaddress, d.addr), d.amount});
+ destinations.push_back({d.address(m_wallet->nettype(), pd.m_payment_id), d.amount});
}
std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
@@ -7902,7 +8082,7 @@ bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vec
uint64_t fee = amount - pd.m_amount_out;
std::vector<std::pair<std::string, uint64_t>> destinations;
for (const auto &d: pd.m_dests) {
- destinations.push_back({get_account_address_as_str(m_wallet->nettype(), d.is_subaddress, d.addr), d.amount});
+ destinations.push_back({d.address(m_wallet->nettype(), pd.m_payment_id), d.amount});
}
std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
@@ -8309,6 +8489,35 @@ void simple_wallet::wallet_idle_thread()
if (!m_idle_run.load(std::memory_order_relaxed))
break;
+#ifndef _WIN32
+ m_inactivity_checker.do_call(boost::bind(&simple_wallet::check_inactivity, this));
+#endif
+ m_refresh_checker.do_call(boost::bind(&simple_wallet::check_refresh, this));
+ m_mms_checker.do_call(boost::bind(&simple_wallet::check_mms, this));
+
+ if (!m_idle_run.load(std::memory_order_relaxed))
+ break;
+ m_idle_cond.wait_for(lock, boost::chrono::seconds(1));
+ }
+}
+//----------------------------------------------------------------------------------------------------
+bool simple_wallet::check_inactivity()
+{
+ // inactivity lock
+ if (!m_locked && !m_in_command)
+ {
+ const uint32_t seconds = m_wallet->inactivity_lock_timeout();
+ if (seconds > 0 && time(NULL) - m_last_activity_time > seconds)
+ {
+ m_locked = true;
+ m_cmd_binder.cancel_input();
+ }
+ }
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool simple_wallet::check_refresh()
+{
// auto refresh
if (m_auto_refresh_enabled)
{
@@ -8323,7 +8532,11 @@ void simple_wallet::wallet_idle_thread()
catch(...) {}
m_auto_refresh_refreshing = false;
}
-
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool simple_wallet::check_mms()
+{
// Check for new MMS messages;
// For simplicity auto message check is ALSO controlled by "m_auto_refresh_enabled" and has no
// separate thread either; thread syncing is tricky enough with only this one idle thread here
@@ -8331,15 +8544,13 @@ void simple_wallet::wallet_idle_thread()
{
check_for_messages();
}
-
- if (!m_idle_run.load(std::memory_order_relaxed))
- break;
- m_idle_cond.wait_for(lock, boost::chrono::seconds(90));
- }
+ return true;
}
//----------------------------------------------------------------------------------------------------
std::string simple_wallet::get_prompt() const
{
+ if (m_locked)
+ return std::string("[") + tr("locked due to inactivity") + "]";
std::string addr_start = m_wallet->get_subaddress_as_str({m_current_subaddress_account, 0}).substr(0, 6);
std::string prompt = std::string("[") + tr("wallet") + " " + addr_start;
if (!m_wallet->check_connection(NULL))
@@ -9007,7 +9218,7 @@ bool simple_wallet::sign(const std::vector<std::string> &args)
std::string filename = args[0];
std::string data;
- bool r = epee::file_io_utils::load_file_to_string(filename, data);
+ bool r = m_wallet->load_from_file(filename, data);
if (!r)
{
fail_msg_writer() << tr("failed to read file ") << filename;
@@ -9033,7 +9244,7 @@ bool simple_wallet::verify(const std::vector<std::string> &args)
std::string signature= args[2];
std::string data;
- bool r = epee::file_io_utils::load_file_to_string(filename, data);
+ bool r = m_wallet->load_from_file(filename, data);
if (!r)
{
fail_msg_writer() << tr("failed to read file ") << filename;
@@ -9059,21 +9270,31 @@ bool simple_wallet::verify(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::export_key_images(const std::vector<std::string> &args)
+bool simple_wallet::export_key_images(const std::vector<std::string> &args_)
{
if (m_wallet->key_on_device())
{
fail_msg_writer() << tr("command not supported by HW wallet");
return true;
}
- if (args.size() != 1)
+ auto args = args_;
+
+ if (m_wallet->watch_only())
{
- PRINT_USAGE(USAGE_EXPORT_KEY_IMAGES);
+ fail_msg_writer() << tr("wallet is watch-only and cannot export key images");
return true;
}
- if (m_wallet->watch_only())
+
+ bool all = false;
+ if (args.size() >= 2 && args[0] == "all")
{
- fail_msg_writer() << tr("wallet is watch-only and cannot export key images");
+ all = true;
+ args.erase(args.begin());
+ }
+
+ if (args.size() != 1)
+ {
+ PRINT_USAGE(USAGE_EXPORT_KEY_IMAGES);
return true;
}
@@ -9085,7 +9306,7 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args)
try
{
- if (!m_wallet->export_key_images(filename))
+ if (!m_wallet->export_key_images(filename, all))
{
fail_msg_writer() << tr("failed to save file ") << filename;
return true;
@@ -9210,13 +9431,22 @@ bool simple_wallet::hw_reconnect(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::export_outputs(const std::vector<std::string> &args)
+bool simple_wallet::export_outputs(const std::vector<std::string> &args_)
{
if (m_wallet->key_on_device())
{
fail_msg_writer() << tr("command not supported by HW wallet");
return true;
}
+ auto args = args_;
+
+ bool all = false;
+ if (args.size() >= 2 && args[0] == "all")
+ {
+ all = true;
+ args.erase(args.begin());
+ }
+
if (args.size() != 1)
{
PRINT_USAGE(USAGE_EXPORT_OUTPUTS);
@@ -9231,8 +9461,8 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args)
try
{
- std::string data = m_wallet->export_outputs_to_str();
- bool r = epee::file_io_utils::save_string_to_file(filename, data);
+ std::string data = m_wallet->export_outputs_to_str(all);
+ bool r = m_wallet->save_to_file(filename, data);
if (!r)
{
fail_msg_writer() << tr("failed to save file ") << filename;
@@ -9265,7 +9495,7 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args)
std::string filename = args[0];
std::string data;
- bool r = epee::file_io_utils::load_file_to_string(filename, data);
+ bool r = m_wallet->load_from_file(filename, data);
if (!r)
{
fail_msg_writer() << tr("failed to read file ") << filename;
@@ -9358,7 +9588,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args)
for (const auto &d: pd.m_dests) {
if (!dests.empty())
dests += ", ";
- dests += get_account_address_as_str(m_wallet->nettype(), d.is_subaddress, d.addr) + ": " + print_money(d.amount);
+ dests += d.address(m_wallet->nettype(), pd.m_payment_id) + ": " + print_money(d.amount);
}
std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
@@ -9466,7 +9696,7 @@ void simple_wallet::commit_or_save(std::vector<tools::wallet2::pending_tx>& ptx_
tx_to_blob(ptx.tx, blob);
const std::string blob_hex = epee::string_tools::buff_to_hex_nodelimer(blob);
const std::string filename = "raw_monero_tx" + (ptx_vector.size() == 1 ? "" : ("_" + std::to_string(i++)));
- if (epee::file_io_utils::save_string_to_file(filename, blob_hex))
+ if (m_wallet->save_to_file(filename, blob_hex, true))
success_msg_writer(true) << tr("Transaction successfully saved to ") << filename << tr(", txid ") << txid;
else
fail_msg_writer() << tr("Failed to save transaction to ") << filename << tr(", txid ") << txid;
@@ -9491,6 +9721,7 @@ int main(int argc, char* argv[])
std::locale::global(boost::locale::generator().generate(""));
boost::filesystem::path::imbue(std::locale());
#endif
+ setlocale(LC_CTYPE, "");
po::options_description desc_params(wallet_args::tr("Wallet options"));
tools::wallet2::init_options(desc_params);
@@ -10251,7 +10482,7 @@ void simple_wallet::mms_export(const std::vector<std::string> &args)
if (valid_id)
{
const std::string filename = "mms_message_content";
- if (epee::file_io_utils::save_string_to_file(filename, m.content))
+ if (m_wallet->save_to_file(filename, m.content))
{
success_msg_writer() << tr("Message content saved to: ") << filename;
}
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 4bf7fa334..22659e99e 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -44,6 +44,7 @@
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "wallet/wallet2.h"
#include "console_handler.h"
+#include "math_helper.h"
#include "wipeable_string.h"
#include "common/i18n.h"
#include "common/password.h"
@@ -109,6 +110,7 @@ namespace cryptonote
bool spendkey(const std::vector<std::string> &args = std::vector<std::string>());
bool seed(const std::vector<std::string> &args = std::vector<std::string>());
bool encrypted_seed(const std::vector<std::string> &args = std::vector<std::string>());
+ bool restore_height(const std::vector<std::string> &args = std::vector<std::string>());
/*!
* \brief Sets seed language.
@@ -143,8 +145,10 @@ namespace cryptonote
bool set_segregation_height(const std::vector<std::string> &args = std::vector<std::string>());
bool set_ignore_fractional_outputs(const std::vector<std::string> &args = std::vector<std::string>());
bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_inactivity_lock_timeout(const std::vector<std::string> &args = std::vector<std::string>());
bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>());
bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_export_format(const std::vector<std::string> &args = std::vector<std::string>());
bool help(const std::vector<std::string> &args = std::vector<std::string>());
bool start_mining(const std::vector<std::string> &args);
bool stop_mining(const std::vector<std::string> &args);
@@ -243,9 +247,11 @@ namespace cryptonote
bool freeze(const std::vector<std::string>& args);
bool thaw(const std::vector<std::string>& args);
bool frozen(const std::vector<std::string>& args);
+ bool lock(const std::vector<std::string>& args);
bool net_stats(const std::vector<std::string>& args);
bool welcome(const std::vector<std::string>& args);
bool version(const std::vector<std::string>& args);
+ bool on_unknown_command(const std::vector<std::string>& args);
bool cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func);
uint64_t get_daemon_blockchain_height(std::string& err);
@@ -262,6 +268,10 @@ namespace cryptonote
std::pair<std::string, std::string> show_outputs_line(const std::vector<uint64_t> &heights, uint64_t blockchain_height, uint64_t highlight_height = std::numeric_limits<uint64_t>::max()) const;
bool freeze_thaw(const std::vector<std::string>& args, bool freeze);
bool prompt_if_old(const std::vector<tools::wallet2::pending_tx> &ptx_vector);
+ bool on_command(bool (simple_wallet::*cmd)(const std::vector<std::string>&), const std::vector<std::string> &args);
+ bool on_empty_command();
+ bool on_cancelled_command();
+ void check_for_inactivity_lock(bool user);
struct transfer_view
{
@@ -309,6 +319,11 @@ namespace cryptonote
void start_background_mining();
void stop_background_mining();
+ // idle thread workers
+ bool check_inactivity();
+ bool check_refresh();
+ bool check_mms();
+
//----------------- i_wallet2_callback ---------------------
virtual void on_new_block(uint64_t height, const cryptonote::block& block);
virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, uint64_t unlock_time);
@@ -416,6 +431,14 @@ namespace cryptonote
uint32_t m_current_subaddress_account;
bool m_long_payment_id_support;
+
+ std::atomic<time_t> m_last_activity_time;
+ std::atomic<bool> m_locked;
+ std::atomic<bool> m_in_command;
+
+ epee::math_helper::once_a_time_seconds<1> m_inactivity_checker;
+ epee::math_helper::once_a_time_seconds<90> m_refresh_checker;
+ epee::math_helper::once_a_time_seconds<90> m_mms_checker;
// MMS
mms::message_store& get_message_store() const { return m_wallet->get_message_store(); };
diff --git a/src/version.cpp.in b/src/version.cpp.in
index 28ce38df7..9121cdf85 100644
--- a/src/version.cpp.in
+++ b/src/version.cpp.in
@@ -2,6 +2,7 @@
#define DEF_MONERO_VERSION "0.14.1.2"
#define DEF_MONERO_RELEASE_NAME "Boron Butterfly"
#define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG
+#define DEF_MONERO_VERSION_IS_RELEASE @VERSION_IS_RELEASE@
#include "version.h"
@@ -9,3 +10,4 @@ const char* const MONERO_VERSION_TAG = DEF_MONERO_VERSION_TAG;
const char* const MONERO_VERSION = DEF_MONERO_VERSION;
const char* const MONERO_RELEASE_NAME = DEF_MONERO_RELEASE_NAME;
const char* const MONERO_VERSION_FULL = DEF_MONERO_VERSION_FULL;
+const bool MONERO_VERSION_IS_RELEASE = DEF_MONERO_VERSION_IS_RELEASE;
diff --git a/src/version.h b/src/version.h
index d1d06c790..b740d3360 100644
--- a/src/version.h
+++ b/src/version.h
@@ -4,3 +4,4 @@ extern const char* const MONERO_VERSION_TAG;
extern const char* const MONERO_VERSION;
extern const char* const MONERO_RELEASE_NAME;
extern const char* const MONERO_VERSION_FULL;
+extern const bool MONERO_VERSION_IS_RELEASE;
diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp
index f4ad8b1f6..ad7029a3c 100644
--- a/src/wallet/api/transaction_history.cpp
+++ b/src/wallet/api/transaction_history.cpp
@@ -181,7 +181,7 @@ void TransactionHistoryImpl::refresh()
// single output transaction might contain multiple transfers
for (const auto &d: pd.m_dests) {
- ti->m_transfers.push_back({d.amount, get_account_address_as_str(m_wallet->m_wallet->nettype(), d.is_subaddress, d.addr)});
+ ti->m_transfers.push_back({d.amount, d.address(m_wallet->m_wallet->nettype(), pd.m_payment_id)});
}
m_history.push_back(ti);
}
diff --git a/src/wallet/message_store.cpp b/src/wallet/message_store.cpp
index 7381005c1..96d4ef3ce 100644
--- a/src/wallet/message_store.cpp
+++ b/src/wallet/message_store.cpp
@@ -397,10 +397,9 @@ void message_store::stop_auto_config()
for (uint32_t i = 0; i < m_num_authorized_signers; ++i)
{
authorized_signer &m = m_signers[i];
- if (!m.me && !m.auto_config_transport_address.empty())
+ if (!m.auto_config_transport_address.empty())
{
- // Try to delete those "unused API" addresses in PyBitmessage, especially since
- // it seems it's not possible to delete them interactively, only to "disable" them
+ // Try to delete the chan that was used for auto-config
m_transporter.delete_transport_address(m.auto_config_transport_address);
}
m.auto_config_token.clear();
@@ -429,14 +428,7 @@ void message_store::setup_signer_for_auto_config(uint32_t index, const std::stri
m.auto_config_token = token;
crypto::hash_to_scalar(token.data(), token.size(), m.auto_config_secret_key);
crypto::secret_key_to_public_key(m.auto_config_secret_key, m.auto_config_public_key);
- if (receiving)
- {
- m.auto_config_transport_address = m_transporter.derive_and_receive_transport_address(m.auto_config_token);
- }
- else
- {
- m.auto_config_transport_address = m_transporter.derive_transport_address(m.auto_config_token);
- }
+ m.auto_config_transport_address = m_transporter.derive_transport_address(m.auto_config_token);
}
bool message_store::get_signer_index_by_monero_address(const cryptonote::account_public_address &monero_address, uint32_t &index) const
diff --git a/src/wallet/message_transporter.cpp b/src/wallet/message_transporter.cpp
index 2f8188a3c..cf9b45b37 100644
--- a/src/wallet/message_transporter.cpp
+++ b/src/wallet/message_transporter.cpp
@@ -192,47 +192,47 @@ bool message_transporter::delete_message(const std::string &transport_id)
return true;
}
-// Deterministically derive a transport / Bitmessage address from 'seed' (the 10-hex-digits
-// auto-config token will be used), but do not set it up for receiving in PyBitmessage as
-// well, because it's possible the address will only ever be used to SEND auto-config data
+// Deterministically derive a new transport address from 'seed' (the 10-hex-digits auto-config
+// token will be used) and set it up for sending and receiving
+// In a first attempt a normal Bitmessage address was used here, but it turned out the
+// key exchange necessary to put it into service could take a long time or even did not
+// work out at all sometimes. Also there were problems when deleting those temporary
+// addresses again after auto-config. Now a chan is used which avoids all these drawbacks
+// quite nicely.
std::string message_transporter::derive_transport_address(const std::string &seed)
{
+ // Don't use the seed directly as chan name; that would be too dangerous, e.g. in the
+ // case of a PyBitmessage instance used by multiple unrelated people
+ // If an auto-config token gets hashed in another context use different salt instead of "chan"
+ std::string salted_seed = seed + "chan";
+ std::string chan_name = epee::string_tools::pod_to_hex(crypto::cn_fast_hash(salted_seed.data(), salted_seed.size()));
+
+ // Calculate the Bitmessage address that the chan will get for being able to
+ // use 'joinChain', as 'createChan' will fail and not tell the address if the chan
+ // already exists (which it can if all auto-config participants share a PyBitmessage
+ // instance). 'joinChan' will also fail in that case, but that won't matter.
std::string request;
start_xml_rpc_cmd(request, "getDeterministicAddress");
- add_xml_rpc_base64_param(request, seed);
+ add_xml_rpc_base64_param(request, chan_name);
add_xml_rpc_integer_param(request, 4); // addressVersionNumber
add_xml_rpc_integer_param(request, 1); // streamNumber
end_xml_rpc_cmd(request);
std::string answer;
post_request(request, answer);
std::string address = get_str_between_tags(answer, "<string>", "</string>");
- return address;
-}
-
-// Derive a transport address and configure it for receiving in PyBitmessage, typically
-// for receiving auto-config messages by the wallet of the auto-config organizer
-std::string message_transporter::derive_and_receive_transport_address(const std::string &seed)
-{
- // We need to call both "get_deterministic_address" AND "createDeterministicAddresses"
- // because we won't get back the address from the latter call if it exists already
- std::string address = derive_transport_address(seed);
- std::string request;
- start_xml_rpc_cmd(request, "createDeterministicAddresses");
- add_xml_rpc_base64_param(request, seed);
- add_xml_rpc_integer_param(request, 1); // numberOfAddresses
- add_xml_rpc_integer_param(request, 4); // addressVersionNumber
+ start_xml_rpc_cmd(request, "joinChan");
+ add_xml_rpc_base64_param(request, chan_name);
+ add_xml_rpc_string_param(request, address);
end_xml_rpc_cmd(request);
- std::string answer;
post_request(request, answer);
-
return address;
}
bool message_transporter::delete_transport_address(const std::string &transport_address)
{
std::string request;
- start_xml_rpc_cmd(request, "deleteAddress");
+ start_xml_rpc_cmd(request, "leaveChan");
add_xml_rpc_string_param(request, transport_address);
end_xml_rpc_cmd(request);
std::string answer;
@@ -270,7 +270,22 @@ bool message_transporter::post_request(const std::string &request, std::string &
std::string string_value = get_str_between_tags(answer, "<string>", "</string>");
if ((string_value.find("API Error") == 0) || (string_value.find("RPC ") == 0))
{
- THROW_WALLET_EXCEPTION(tools::error::bitmessage_api_error, string_value);
+ if ((string_value.find("API Error 0021") == 0) && (request.find("joinChan") != std::string::npos))
+ {
+ // Error that occurs if one tries to join an already joined chan, which can happen
+ // if several auto-config participants share one PyBitmessage instance: As a little
+ // hack simply ignore the error. (A clean solution would be to check for the chan
+ // with 'listAddresses2', but parsing the returned array is much more complicated.)
+ }
+ else if ((string_value.find("API Error 0013") == 0) && (request.find("leaveChan") != std::string::npos))
+ {
+ // Error that occurs if one tries to leave an already left / deleted chan, which can happen
+ // if several auto-config participants share one PyBitmessage instance: Also ignore.
+ }
+ else
+ {
+ THROW_WALLET_EXCEPTION(tools::error::bitmessage_api_error, string_value);
+ }
}
return r;
diff --git a/src/wallet/message_transporter.h b/src/wallet/message_transporter.h
index 736fc9b63..28c099d87 100644
--- a/src/wallet/message_transporter.h
+++ b/src/wallet/message_transporter.h
@@ -91,7 +91,6 @@ public:
bool delete_message(const std::string &transport_id);
void stop() { m_run.store(false, std::memory_order_relaxed); }
std::string derive_transport_address(const std::string &seed);
- std::string derive_and_receive_transport_address(const std::string &seed);
bool delete_transport_address(const std::string &transport_address);
private:
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 117ad4ea8..b99fc12e2 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -40,6 +40,7 @@
#include <boost/asio/ip/address.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/preprocessor/stringize.hpp>
+#include <openssl/evp.h>
#include "include_base_utils.h"
using namespace epee;
@@ -135,9 +136,13 @@ using namespace cryptonote;
#define DEFAULT_MIN_OUTPUT_COUNT 5
#define DEFAULT_MIN_OUTPUT_VALUE (2*COIN)
+#define DEFAULT_INACTIVITY_LOCK_TIMEOUT 90 // a minute and a half
+
static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1";
static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1";
+static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1";
+
namespace
{
std::string get_default_ringdb_path()
@@ -275,6 +280,7 @@ struct options {
const command_line::arg_descriptor<std::string> tx_notify = { "tx-notify" , "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" , "" };
const command_line::arg_descriptor<bool> no_dns = {"no-dns", tools::wallet2::tr("Do not use DNS"), false};
const command_line::arg_descriptor<bool> offline = {"offline", tools::wallet2::tr("Do not connect to a daemon, nor use DNS"), false};
+ const command_line::arg_descriptor<std::string> extra_entropy = {"extra-entropy", tools::wallet2::tr("File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, wihch typically means more than 256 bits of data)")};
};
void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file, std::string &mms_file)
@@ -476,6 +482,15 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
if (command_line::get_arg(vm, opts.offline))
wallet->set_offline();
+ const std::string extra_entropy = command_line::get_arg(vm, opts.extra_entropy);
+ if (!extra_entropy.empty())
+ {
+ std::string data;
+ THROW_WALLET_EXCEPTION_IF(!epee::file_io_utils::load_file_to_string(extra_entropy, data),
+ tools::error::wallet_internal_error, "Failed to load extra entropy from " + extra_entropy);
+ add_extra_entropy_thread_safe(data.data(), data.size());
+ }
+
try
{
if (!command_line::is_arg_defaulted(vm, opts.tx_notify))
@@ -1114,6 +1129,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_segregation_height(0),
m_ignore_fractional_outputs(true),
m_track_uses(false),
+ m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT),
m_setup_background_mining(BackgroundMiningMaybe),
m_is_initialized(false),
m_kdf_rounds(kdf_rounds),
@@ -1143,7 +1159,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_device_last_key_image_sync(0),
m_use_dns(true),
m_offline(false),
- m_rpc_version(0)
+ m_rpc_version(0),
+ m_export_format(ExportFormat::Binary)
{
}
@@ -1200,6 +1217,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par
command_line::add_arg(desc_params, opts.tx_notify);
command_line::add_arg(desc_params, opts.no_dns);
command_line::add_arg(desc_params, opts.offline);
+ command_line::add_arg(desc_params, opts.extra_entropy);
}
std::pair<std::unique_ptr<wallet2>, tools::password_container> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
@@ -1507,7 +1525,9 @@ void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, co
//----------------------------------------------------------------------------------------------------
void wallet2::set_subaddress_lookahead(size_t major, size_t minor)
{
+ THROW_WALLET_EXCEPTION_IF(major == 0, error::wallet_internal_error, "Subaddress major lookahead may not be zero");
THROW_WALLET_EXCEPTION_IF(major > 0xffffffff, error::wallet_internal_error, "Subaddress major lookahead is too large");
+ THROW_WALLET_EXCEPTION_IF(minor == 0, error::wallet_internal_error, "Subaddress minor lookahead may not be zero");
THROW_WALLET_EXCEPTION_IF(minor > 0xffffffff, error::wallet_internal_error, "Subaddress minor lookahead is too large");
m_subaddress_lookahead_major = major;
m_subaddress_lookahead_minor = minor;
@@ -3635,6 +3655,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
value2.SetInt(m_track_uses ? 1 : 0);
json.AddMember("track_uses", value2, json.GetAllocator());
+ value2.SetInt(m_inactivity_lock_timeout);
+ json.AddMember("inactivity_lock_timeout", value2, json.GetAllocator());
+
value2.SetInt(m_setup_background_mining);
json.AddMember("setup_background_mining", value2, json.GetAllocator());
@@ -3647,6 +3670,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
value2.SetInt(m_original_keys_available ? 1 : 0);
json.AddMember("original_keys_available", value2, json.GetAllocator());
+ value2.SetInt(m_export_format);
+ json.AddMember("export_format", value2, json.GetAllocator());
+
value2.SetUint(1);
json.AddMember("encrypted_secret_keys", value2, json.GetAllocator());
@@ -3684,7 +3710,7 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
std::string tmp_file_name = keys_file_name + ".new";
std::string buf;
r = ::serialization::dump_binary(keys_file_data, buf);
- r = r && epee::file_io_utils::save_string_to_file(tmp_file_name, buf);
+ r = r && save_to_file(tmp_file_name, buf);
CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name);
unlock_keys_file();
@@ -3741,7 +3767,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
wallet2::keys_file_data keys_file_data;
std::string buf;
bool encrypted_secret_keys = false;
- bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf);
+ bool r = load_from_file(keys_file_name, buf);
THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name);
// Decrypt the contents
@@ -3754,7 +3780,6 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject())
crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
-
// The contents should be JSON if the wallet follows the new format.
if (json.Parse(account_data.c_str()).HasParseError())
{
@@ -3789,10 +3814,12 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_segregation_height = 0;
m_ignore_fractional_outputs = true;
m_track_uses = false;
+ m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT;
m_setup_background_mining = BackgroundMiningMaybe;
m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR;
m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR;
m_original_keys_available = false;
+ m_export_format = ExportFormat::Binary;
m_device_name = "";
m_device_derivation_path = "";
m_key_device_type = hw::device::device_type::SOFTWARE;
@@ -3944,6 +3971,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_ignore_fractional_outputs = field_ignore_fractional_outputs;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false);
m_track_uses = field_track_uses;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, inactivity_lock_timeout, uint32_t, Uint, false, DEFAULT_INACTIVITY_LOCK_TIMEOUT);
+ m_inactivity_lock_timeout = field_inactivity_lock_timeout;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, setup_background_mining, BackgroundMiningSetupType, Int, false, BackgroundMiningMaybe);
m_setup_background_mining = field_setup_background_mining;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_major, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MAJOR);
@@ -3954,6 +3983,9 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, encrypted_secret_keys, uint32_t, Uint, false, false);
encrypted_secret_keys = field_encrypted_secret_keys;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, export_format, ExportFormat, Int, false, Binary);
+ m_export_format = field_export_format;
+
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_name, std::string, String, false, std::string());
if (m_device_name.empty())
{
@@ -4101,7 +4133,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
wallet2::keys_file_data keys_file_data;
std::string buf;
bool encrypted_secret_keys = false;
- bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf);
+ bool r = load_from_file(keys_file_name, buf);
THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name);
// Decrypt the contents
@@ -4186,7 +4218,7 @@ void wallet2::create_keys_file(const std::string &wallet_, bool watch_only, cons
if (create_address_file)
{
- r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype));
+ r = save_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype), true);
if(!r) MERROR("String with address text not saved");
}
}
@@ -4208,7 +4240,7 @@ bool wallet2::query_device(hw::device::device_type& device_type, const std::stri
rapidjson::Document json;
wallet2::keys_file_data keys_file_data;
std::string buf;
- bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf);
+ bool r = load_from_file(keys_file_name, buf);
THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name);
// Decrypt the contents
@@ -4757,7 +4789,7 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
if (boost::filesystem::exists(m_wallet_file + ".address.txt"))
{
- r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype));
+ r = save_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype), true);
if(!r) MERROR("String with address text not saved");
}
}
@@ -5266,7 +5298,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
{
wallet2::cache_file_data cache_file_data;
std::string buf;
- bool r = epee::file_io_utils::load_file_to_string(m_wallet_file, buf, std::numeric_limits<size_t>::max());
+ bool r = load_from_file(m_wallet_file, buf, std::numeric_limits<size_t>::max());
THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file);
// try to read it as an encrypted cache
@@ -5501,7 +5533,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
{
// save address to the new file
const std::string address_file = m_wallet_file + ".address.txt";
- r = file_io_utils::save_string_to_file(address_file, m_account.get_public_address_str(m_nettype));
+ r = save_to_file(address_file, m_account.get_public_address_str(m_nettype), true);
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_wallet_file);
}
// remove old wallet file
@@ -5536,7 +5568,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
binary_archive<true> oar(oss);
bool success = ::serialization::serialize(oar, cache_file_data);
if (success) {
- success = epee::file_io_utils::save_string_to_file(new_file, oss.str());
+ success = save_to_file(new_file, oss.str());
}
THROW_WALLET_EXCEPTION_IF(!success, error::file_save_error, new_file);
#else
@@ -6164,7 +6196,7 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri
std::string ciphertext = dump_tx_to_str(ptx_vector);
if (ciphertext.empty())
return false;
- return epee::file_io_utils::save_string_to_file(filename, ciphertext);
+ return save_to_file(filename, ciphertext);
}
//----------------------------------------------------------------------------------------------------
std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) const
@@ -6206,7 +6238,7 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
LOG_PRINT_L0("File " << unsigned_filename << " does not exist: " << errcode);
return false;
}
- if (!epee::file_io_utils::load_file_to_string(unsigned_filename.c_str(), s))
+ if (!load_from_file(unsigned_filename.c_str(), s))
{
LOG_PRINT_L0("Failed to load from " << unsigned_filename);
return false;
@@ -6427,7 +6459,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
return false;
}
- if (!epee::file_io_utils::save_string_to_file(signed_filename, ciphertext))
+ if (!save_to_file(signed_filename, ciphertext))
{
LOG_PRINT_L0("Failed to save file to " << signed_filename);
return false;
@@ -6439,7 +6471,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
{
std::string tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(signed_txes.ptx[i].tx));
std::string raw_filename = signed_filename + "_raw" + (signed_txes.ptx.size() == 1 ? "" : ("_" + std::to_string(i)));
- if (!epee::file_io_utils::save_string_to_file(raw_filename, tx_as_hex))
+ if (!save_to_file(raw_filename, tx_as_hex))
{
LOG_PRINT_L0("Failed to save file to " << raw_filename);
return false;
@@ -6487,7 +6519,7 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
return false;
}
- if (!epee::file_io_utils::load_file_to_string(signed_filename.c_str(), s))
+ if (!load_from_file(signed_filename.c_str(), s))
{
LOG_PRINT_L0("Failed to load from " << signed_filename);
return false;
@@ -6618,7 +6650,7 @@ bool wallet2::save_multisig_tx(const multisig_tx_set &txs, const std::string &fi
std::string ciphertext = save_multisig_tx(txs);
if (ciphertext.empty())
return false;
- return epee::file_io_utils::save_string_to_file(filename, ciphertext);
+ return save_to_file(filename, ciphertext);
}
//----------------------------------------------------------------------------------------------------
wallet2::multisig_tx_set wallet2::make_multisig_tx_set(const std::vector<pending_tx>& ptx_vector) const
@@ -6646,7 +6678,7 @@ bool wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const
std::string ciphertext = save_multisig_tx(ptx_vector);
if (ciphertext.empty())
return false;
- return epee::file_io_utils::save_string_to_file(filename, ciphertext);
+ return save_to_file(filename, ciphertext);
}
//----------------------------------------------------------------------------------------------------
bool wallet2::parse_multisig_tx_from_str(std::string multisig_tx_st, multisig_tx_set &exported_txs) const
@@ -6737,7 +6769,7 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t
LOG_PRINT_L0("File " << filename << " does not exist: " << errcode);
return false;
}
- if (!epee::file_io_utils::load_file_to_string(filename.c_str(), s))
+ if (!load_from_file(filename.c_str(), s))
{
LOG_PRINT_L0("Failed to load from " << filename);
return false;
@@ -11607,10 +11639,10 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
return tx_pub_key;
}
-bool wallet2::export_key_images(const std::string &filename) const
+bool wallet2::export_key_images(const std::string &filename, bool all) const
{
PERF_TIMER(export_key_images);
- std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = export_key_images();
+ std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = export_key_images(all);
std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC));
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
const uint32_t offset = ski.first;
@@ -11633,7 +11665,7 @@ bool wallet2::export_key_images(const std::string &filename) const
// encrypt data, keep magic plaintext
PERF_TIMER(export_key_images_encrypt);
std::string ciphertext = encrypt_with_view_secret_key(data);
- return epee::file_io_utils::save_string_to_file(filename, magic + ciphertext);
+ return save_to_file(filename, magic + ciphertext);
}
//----------------------------------------------------------------------------------------------------
@@ -11698,7 +11730,7 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent
{
PERF_TIMER(import_key_images_fsu);
std::string data;
- bool r = epee::file_io_utils::load_file_to_string(filename, data);
+ bool r = load_from_file(filename, data);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename);
@@ -13102,6 +13134,83 @@ void wallet2::throw_on_rpc_response_error(const boost::optional<std::string> &st
THROW_WALLET_EXCEPTION_IF(*status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, method, m_trusted_daemon ? *status : "daemon error");
}
//----------------------------------------------------------------------------------------------------
+
+bool wallet2::save_to_file(const std::string& path_to_file, const std::string& raw, bool is_printable) const
+{
+ if (is_printable || m_export_format == ExportFormat::Binary)
+ {
+ return epee::file_io_utils::save_string_to_file(path_to_file, raw);
+ }
+
+ FILE *fp = fopen(path_to_file.c_str(), "w+");
+ // Save the result b/c we need to close the fp before returning success/failure.
+ int write_result = PEM_write(fp, ASCII_OUTPUT_MAGIC.c_str(), "", (const unsigned char *) raw.c_str(), raw.length());
+ fclose(fp);
+
+ if (write_result == 0)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+//----------------------------------------------------------------------------------------------------
+
+bool wallet2::load_from_file(const std::string& path_to_file, std::string& target_str,
+ size_t max_size)
+{
+ std::string data;
+ bool r = epee::file_io_utils::load_file_to_string(path_to_file, data, max_size);
+ if (!r)
+ {
+ return false;
+ }
+
+ if (!boost::algorithm::contains(boost::make_iterator_range(data.begin(), data.end()), ASCII_OUTPUT_MAGIC))
+ {
+ // It's NOT our ascii dump.
+ target_str = std::move(data);
+ return true;
+ }
+
+ // Creating a BIO and calling PEM_read_bio instead of simpler PEM_read
+ // to avoid reading the file from disk twice.
+ BIO* b = BIO_new_mem_buf((const void*) data.data(), data.length());
+
+ char *name = NULL;
+ char *header = NULL;
+ unsigned char *openssl_data = NULL;
+ long len = 0;
+
+ // Save the result b/c we need to free the data before returning success/failure.
+ int success = PEM_read_bio(b, &name, &header, &openssl_data, &len);
+
+ try
+ {
+ target_str = std::string((const char*) openssl_data, len);
+ }
+ catch (...)
+ {
+ success = 0;
+ }
+
+ OPENSSL_free((void *) name);
+ OPENSSL_free((void *) header);
+ OPENSSL_free((void *) openssl_data);
+ BIO_free(b);
+
+ if (success == 0)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::hash_m_transfer(const transfer_details & transfer, crypto::hash &hash) const
{
KECCAK_CTX state;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index a6d042297..5607602e7 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -227,6 +227,11 @@ private:
BackgroundMiningNo = 2,
};
+ enum ExportFormat {
+ Binary = 0,
+ Ascii,
+ };
+
static const char* tr(const char* str);
static bool has_testnet_option(const boost::program_options::variables_map& vm);
@@ -1046,10 +1051,14 @@ private:
void track_uses(bool value) { m_track_uses = value; }
BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; }
void setup_background_mining(BackgroundMiningSetupType value) { m_setup_background_mining = value; }
+ uint32_t inactivity_lock_timeout() const { return m_inactivity_lock_timeout; }
+ void inactivity_lock_timeout(uint32_t seconds) { m_inactivity_lock_timeout = seconds; }
const std::string & device_name() const { return m_device_name; }
void device_name(const std::string & device_name) { m_device_name = device_name; }
const std::string & device_derivation_path() const { return m_device_derivation_path; }
void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; }
+ const ExportFormat & export_format() const { return m_export_format; }
+ inline void set_export_format(const ExportFormat& export_format) { m_export_format = export_format; }
bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys);
@@ -1175,7 +1184,7 @@ private:
void import_payments_out(const std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>> &confirmed_payments);
std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> export_blockchain() const;
void import_blockchain(const std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> &bc);
- bool export_key_images(const std::string &filename) const;
+ bool export_key_images(const std::string &filename, bool all = false) const;
std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> export_key_images(bool all = false) const;
uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true);
uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent);
@@ -1300,6 +1309,9 @@ private:
bool frozen(const crypto::key_image &ki) const;
bool frozen(const transfer_details &td) const;
+ bool save_to_file(const std::string& path_to_file, const std::string& binary, bool is_printable = false) const;
+ static bool load_from_file(const std::string& path_to_file, std::string& target_str, size_t max_size = 1000000000);
+
uint64_t get_bytes_sent() const;
uint64_t get_bytes_received() const;
@@ -1496,6 +1508,7 @@ private:
uint64_t m_segregation_height;
bool m_ignore_fractional_outputs;
bool m_track_uses;
+ uint32_t m_inactivity_lock_timeout;
BackgroundMiningSetupType m_setup_background_mining;
bool m_is_initialized;
NodeRPCProxy m_node_rpc_proxy;
@@ -1546,6 +1559,8 @@ private:
std::shared_ptr<tools::Notify> m_tx_notify;
std::unique_ptr<wallet_device_callback> m_device_callback;
+
+ ExportFormat m_export_format;
};
}
BOOST_CLASS_VERSION(tools::wallet2, 28)
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 844ecf90c..7c87e7114 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -36,6 +36,7 @@
#include "include_base_utils.h"
using namespace epee;
+#include "version.h"
#include "wallet_rpc_server.h"
#include "wallet/wallet_args.h"
#include "common/command_line.h"
@@ -352,7 +353,7 @@ namespace tools
entry.destinations.push_back(wallet_rpc::transfer_destination());
wallet_rpc::transfer_destination &td = entry.destinations.back();
td.amount = d.amount;
- td.address = d.original.empty() ? get_account_address_as_str(m_wallet->nettype(), d.is_subaddress, d.addr) : d.original;
+ td.address = d.address(m_wallet->nettype(), pd.m_payment_id);
}
entry.type = "out";
@@ -382,7 +383,7 @@ namespace tools
entry.destinations.push_back(wallet_rpc::transfer_destination());
wallet_rpc::transfer_destination &td = entry.destinations.back();
td.amount = d.amount;
- td.address = d.original.empty() ? get_account_address_as_str(m_wallet->nettype(), d.is_subaddress, d.addr) : d.original;
+ td.address = d.address(m_wallet->nettype(), pd.m_payment_id);
}
entry.type = is_failed ? "failed" : "pending";
@@ -4188,6 +4189,7 @@ namespace tools
bool wallet_rpc_server::on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
res.version = WALLET_RPC_VERSION;
+ res.release = MONERO_VERSION_IS_RELEASE;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 4504ac752..2dfe6db85 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -47,7 +47,7 @@
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define WALLET_RPC_VERSION_MAJOR 1
-#define WALLET_RPC_VERSION_MINOR 13
+#define WALLET_RPC_VERSION_MINOR 14
#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR)
namespace tools
@@ -2418,9 +2418,11 @@ namespace wallet_rpc
struct response_t
{
uint32_t version;
+ bool release;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(version)
+ KV_SERIALIZE(release)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<response_t> response;