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/cryptonote_basic/connection_context.h1
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_defs.h2
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.h2
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl54
-rw-r--r--src/cryptonote_protocol/levin_notify.cpp2
-rw-r--r--src/daemon/command_parser_executor.cpp42
-rw-r--r--src/daemon/command_server.cpp4
-rw-r--r--src/p2p/net_node.inl16
-rw-r--r--src/ringct/bulletproofs.cc2
-rw-r--r--src/rpc/core_rpc_server.cpp1
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h4
-rw-r--r--src/simplewallet/simplewallet.cpp30
13 files changed, 145 insertions, 19 deletions
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 8aa958825..4207bb2de 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -3001,7 +3001,7 @@ bool BlockchainLMDB::tx_exists(const crypto::hash& h) const
if (! tx_found)
{
- LOG_PRINT_L1("transaction with hash " << epee::string_tools::pod_to_hex(h) << " not found in db");
+ LOG_PRINT_L3("transaction with hash " << epee::string_tools::pod_to_hex(h) << " not found in db");
return false;
}
@@ -3032,7 +3032,7 @@ bool BlockchainLMDB::tx_exists(const crypto::hash& h, uint64_t& tx_id) const
bool ret = false;
if (get_result == MDB_NOTFOUND)
{
- LOG_PRINT_L1("transaction with hash " << epee::string_tools::pod_to_hex(h) << " not found in db");
+ LOG_PRINT_L3("transaction with hash " << epee::string_tools::pod_to_hex(h) << " not found in db");
}
else if (get_result)
throw0(DB_ERROR(lmdb_error("DB error attempting to fetch transaction from hash", get_result).c_str()));
diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h
index 8d26e5638..dfc59631f 100644
--- a/src/cryptonote_basic/connection_context.h
+++ b/src/cryptonote_basic/connection_context.h
@@ -69,6 +69,7 @@ namespace cryptonote
bool m_anchor;
int32_t m_score;
int m_expect_response;
+ uint64_t m_expect_height;
};
inline std::string get_protocol_state_string(cryptonote_connection_context::state s)
diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h
index 9687b07a6..8c511e824 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_defs.h
+++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h
@@ -298,6 +298,7 @@ namespace cryptonote
uint64_t cumulative_difficulty_top64;
std::vector<crypto::hash> m_block_ids;
std::vector<uint64_t> m_block_weights;
+ cryptonote::blobdata first_block;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(start_height)
@@ -309,6 +310,7 @@ namespace cryptonote
KV_SERIALIZE_OPT(cumulative_difficulty_top64, (uint64_t)0)
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids)
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_weights)
+ KV_SERIALIZE(first_block)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h
index 6368f8190..8f867eee6 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.h
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h
@@ -117,6 +117,8 @@ namespace cryptonote
std::string get_peers_overview() const;
std::pair<uint32_t, uint32_t> get_next_needed_pruning_stripe() const;
bool needs_new_sync_connections() const;
+ bool is_busy_syncing();
+
private:
//----------------- commands handlers ----------------------------------------------
int handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context);
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index 27f8888bb..e2598e382 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -141,6 +141,7 @@ namespace cryptonote
{
NOTIFY_REQUEST_CHAIN::request r = {};
context.m_needed_objects.clear();
+ context.m_expect_height = m_core.get_current_blockchain_height();
m_core.get_short_chain_history(r.block_ids);
handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?)
r.prune = m_sync_pruned_blocks;
@@ -493,6 +494,7 @@ namespace cryptonote
context.m_needed_objects.clear();
context.m_state = cryptonote_connection_context::state_synchronizing;
NOTIFY_REQUEST_CHAIN::request r = {};
+ context.m_expect_height = m_core.get_current_blockchain_height();
m_core.get_short_chain_history(r.block_ids);
r.prune = m_sync_pruned_blocks;
handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?)
@@ -773,6 +775,7 @@ namespace cryptonote
context.m_needed_objects.clear();
context.m_state = cryptonote_connection_context::state_synchronizing;
NOTIFY_REQUEST_CHAIN::request r = {};
+ context.m_expect_height = m_core.get_current_blockchain_height();
m_core.get_short_chain_history(r.block_ids);
handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?)
r.prune = m_sync_pruned_blocks;
@@ -1168,7 +1171,16 @@ namespace cryptonote
return 1;
}
if (start_height == std::numeric_limits<uint64_t>::max())
+ {
start_height = boost::get<txin_gen>(b.miner_tx.vin[0]).height;
+ if (start_height > context.m_expect_height)
+ {
+ LOG_ERROR_CCONTEXT("sent block ahead of expected height, dropping connection");
+ drop_connection(context, false, false);
+ ++m_sync_bad_spans_downloaded;
+ return 1;
+ }
+ }
auto req_it = context.m_requested_objects.find(block_hash);
if(req_it == context.m_requested_objects.end())
@@ -1714,6 +1726,7 @@ skip:
LOG_PRINT_CCONTEXT_L2("requesting callback");
context.m_last_request_time = boost::date_time::not_a_date_time;
context.m_expect_response = 0;
+ context.m_expect_height = 0;
context.m_state = cryptonote_connection_context::state_standby; // we'll go back to adding, then (if we can't), download
++context.m_callback_request_count;
m_p2p->request_callback(context);
@@ -1813,6 +1826,16 @@ skip:
LOG_ERROR_CCONTEXT("Failed to handle NOTIFY_REQUEST_CHAIN.");
return 1;
}
+ if (r.m_block_ids.size() >= 2)
+ {
+ cryptonote::block b;
+ if (!m_core.get_block_by_hash(r.m_block_ids[1], b))
+ {
+ LOG_ERROR_CCONTEXT("Failed to handle NOTIFY_REQUEST_CHAIN: first block not found");
+ return 1;
+ }
+ r.first_block = cryptonote::block_to_blob(b);
+ }
MLOG_P2P_MESSAGE("-->>NOTIFY_RESPONSE_CHAIN_ENTRY: m_start_height=" << r.start_height << ", m_total_height=" << r.total_height << ", m_block_ids.size()=" << r.m_block_ids.size());
post_notify<NOTIFY_RESPONSE_CHAIN_ENTRY>(r, context);
return 1;
@@ -2259,6 +2282,7 @@ skip:
}
}
context.m_last_request_time = boost::posix_time::microsec_clock::universal_time();
+ context.m_expect_height = span.first;
context.m_expect_response = NOTIFY_RESPONSE_GET_OBJECTS::ID;
MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_GET_OBJECTS: blocks.size()=" << req.blocks.size()
<< "requested blocks count=" << count << " / " << count_limit << " from " << span.first << ", first hash " << req.blocks.front());
@@ -2331,6 +2355,7 @@ skip:
{//we have to fetch more objects ids, request blockchain entry
NOTIFY_REQUEST_CHAIN::request r = {};
+ context.m_expect_height = m_core.get_current_blockchain_height();
m_core.get_short_chain_history(r.block_ids);
CHECK_AND_ASSERT_MES(!r.block_ids.empty(), false, "Short chain history is empty");
@@ -2338,7 +2363,10 @@ skip:
{
// we'll want to start off from where we are on that peer, which may not be added yet
if (context.m_last_known_hash != crypto::null_hash && r.block_ids.front() != context.m_last_known_hash)
+ {
+ context.m_expect_height = std::numeric_limits<uint64_t>::max();
r.block_ids.push_front(context.m_last_known_hash);
+ }
}
handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?)
@@ -2480,6 +2508,12 @@ skip:
return 1;
}
context.m_expect_response = 0;
+ if (arg.start_height + 1 > context.m_expect_height) // we expect an overlapping block
+ {
+ LOG_ERROR_CCONTEXT("Got NOTIFY_RESPONSE_CHAIN_ENTRY past expected height, dropping connection");
+ drop_connection(context, true, false);
+ return 1;
+ }
context.m_last_request_time = boost::date_time::not_a_date_time;
@@ -2491,7 +2525,7 @@ skip:
drop_connection(context, true, false);
return 1;
}
- if (arg.total_height < arg.m_block_ids.size() || arg.start_height > arg.total_height - arg.m_block_ids.size() || arg.start_height >= m_core.get_current_blockchain_height())
+ if (arg.total_height < arg.m_block_ids.size() || arg.start_height > arg.total_height - arg.m_block_ids.size())
{
LOG_ERROR_CCONTEXT("sent invalid start/nblocks/height, dropping connection");
drop_connection(context, true, false);
@@ -2527,6 +2561,17 @@ skip:
return 1;
}
+ std::unordered_set<crypto::hash> hashes;
+ for (const auto &h: arg.m_block_ids)
+ {
+ if (!hashes.insert(h).second)
+ {
+ LOG_ERROR_CCONTEXT("sent duplicate block, dropping connection");
+ drop_connection(context, true, false);
+ return 1;
+ }
+ }
+
uint64_t n_use_blocks = m_core.prevalidate_block_hashes(arg.start_height, arg.m_block_ids, arg.m_block_weights);
if (n_use_blocks == 0 || n_use_blocks + HASH_OF_HASHES_STEP <= arg.m_block_ids.size())
{
@@ -2726,6 +2771,13 @@ skip:
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
+ bool t_cryptonote_protocol_handler<t_core>::is_busy_syncing()
+ {
+ const boost::unique_lock<boost::mutex> sync{m_sync_lock, boost::try_to_lock};
+ return !sync.owns_lock();
+ }
+ //------------------------------------------------------------------------------------------------------------------------
+ template<class t_core>
void t_cryptonote_protocol_handler<t_core>::drop_connection_with_score(cryptonote_connection_context &context, unsigned score, bool flush_all_spans)
{
LOG_DEBUG_CC(context, "dropping connection id " << context.m_connection_id << " (pruning seed " <<
diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp
index 318bcf4e1..21363972d 100644
--- a/src/cryptonote_protocol/levin_notify.cpp
+++ b/src/cryptonote_protocol/levin_notify.cpp
@@ -287,7 +287,6 @@ namespace levin
connection_count(0),
flush_callbacks(0),
nzone(zone),
- is_public(is_public),
pad_txs(pad_txs),
fluffing(false)
{
@@ -305,7 +304,6 @@ namespace levin
std::atomic<std::size_t> connection_count; //!< Only update in strand, can be read at any time
std::uint32_t flush_callbacks; //!< Number of active fluff flush callbacks queued
const epee::net_utils::zone nzone; //!< Zone is public ipv4/ipv6 connections, or i2p or tor
- const bool is_public; //!< Zone is public ipv4/ipv6 connections
const bool pad_txs; //!< Pad txs to the next boundary for privacy
bool fluffing; //!< Zone is in Dandelion++ fluff epoch
};
diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp
index aac891c4a..767ce7bc6 100644
--- a/src/daemon/command_parser_executor.cpp
+++ b/src/daemon/command_parser_executor.cpp
@@ -28,6 +28,7 @@
#include "common/dns_utils.h"
#include "common/command_line.h"
+#include "net/parse.h"
#include "daemon/command_parser_executor.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
@@ -657,7 +658,6 @@ bool t_command_parser_executor::ban(const std::vector<std::string>& args)
std::cout << "Invalid syntax: Expects one or two parameters. For more details, use the help command." << std::endl;
return true;
}
- std::string ip = args[0];
time_t seconds = P2P_IP_BLOCKTIME;
if (args.size() > 1)
{
@@ -676,7 +676,45 @@ bool t_command_parser_executor::ban(const std::vector<std::string>& args)
return true;
}
}
- return m_executor.ban(ip, seconds);
+ if (boost::starts_with(args[0], "@"))
+ {
+ const std::string ban_list = args[0].substr(1);
+
+ try
+ {
+ const boost::filesystem::path ban_list_path(ban_list);
+ boost::system::error_code ec;
+ if (!boost::filesystem::exists(ban_list_path, ec))
+ {
+ std::cout << "Can't find ban list file " + ban_list + " - " + ec.message() << std::endl;
+ return true;
+ }
+
+ bool ret = true;
+ std::ifstream ifs(ban_list_path.string());
+ for (std::string line; std::getline(ifs, line); )
+ {
+ const expect<epee::net_utils::network_address> parsed_addr = net::get_network_address(line, 0);
+ if (!parsed_addr)
+ {
+ std::cout << "Invalid IP address: " << line << " - " << parsed_addr.error() << std::endl;
+ continue;
+ }
+ ret &= m_executor.ban(parsed_addr->host_str(), seconds);
+ }
+ return ret;
+ }
+ catch (const std::exception &e)
+ {
+ std::cout << "Error loading ban list: " << e.what() << std::endl;
+ return false;
+ }
+ }
+ else
+ {
+ const std::string ip = args[0];
+ return m_executor.ban(ip, seconds);
+ }
}
bool t_command_parser_executor::unban(const std::vector<std::string>& args)
diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp
index dd5f76188..4768bb842 100644
--- a/src/daemon/command_server.cpp
+++ b/src/daemon/command_server.cpp
@@ -234,8 +234,8 @@ t_command_server::t_command_server(
m_command_lookup.set_handler(
"ban"
, std::bind(&t_command_parser_executor::ban, &m_parser, p::_1)
- , "ban <IP> [<seconds>]"
- , "Ban a given <IP> for a given amount of <seconds>."
+ , "ban [<IP>|@<filename>] [<seconds>]"
+ , "Ban a given <IP> or list of IPs from a file for a given amount of <seconds>."
);
m_command_lookup.set_handler(
"unban"
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index e932afcd7..4ea141bb8 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -812,6 +812,7 @@ namespace nodetool
"zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083",
};
}
+ return {};
case epee::net_utils::zone::i2p:
if (m_nettype == cryptonote::MAINNET)
{
@@ -819,7 +820,8 @@ namespace nodetool
"s3l6ke4ed3df466khuebb4poienoingwof7oxtbo6j4n56sghe3a.b32.i2p:18080",
"sel36x6fibfzujwvt4hf5gxolz6kd3jpvbjqg6o3ud2xtionyl2q.b32.i2p:18080"
};
- }
+ }
+ return {};
default:
break;
}
@@ -2139,7 +2141,9 @@ namespace nodetool
LOG_DEBUG_CC(context, "REMOTE PEERLIST: remote peerlist size=" << peerlist_.size());
LOG_TRACE_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_, [this](const peerlist_entry &pe) { return !is_addr_recently_failed(pe.adr); });
+ return m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.merge_peerlist(peerlist_, [this](const peerlist_entry &pe) {
+ return !is_addr_recently_failed(pe.adr) && is_remote_host_allowed(pe.adr);
+ });
}
//-----------------------------------------------------------------------------------
template<class t_payload_net_handler>
@@ -2884,8 +2888,8 @@ namespace nodetool
const uint32_t index = stripe - 1;
CRITICAL_REGION_LOCAL(m_used_stripe_peers_mutex);
MINFO("adding stripe " << stripe << " peer: " << context.m_remote_address.str());
- std::remove_if(m_used_stripe_peers[index].begin(), m_used_stripe_peers[index].end(),
- [&context](const epee::net_utils::network_address &na){ return context.m_remote_address == na; });
+ m_used_stripe_peers[index].erase(std::remove_if(m_used_stripe_peers[index].begin(), m_used_stripe_peers[index].end(),
+ [&context](const epee::net_utils::network_address &na){ return context.m_remote_address == na; }), m_used_stripe_peers[index].end());
m_used_stripe_peers[index].push_back(context.m_remote_address);
}
@@ -2898,8 +2902,8 @@ namespace nodetool
const uint32_t index = stripe - 1;
CRITICAL_REGION_LOCAL(m_used_stripe_peers_mutex);
MINFO("removing stripe " << stripe << " peer: " << context.m_remote_address.str());
- std::remove_if(m_used_stripe_peers[index].begin(), m_used_stripe_peers[index].end(),
- [&context](const epee::net_utils::network_address &na){ return context.m_remote_address == na; });
+ m_used_stripe_peers[index].erase(std::remove_if(m_used_stripe_peers[index].begin(), m_used_stripe_peers[index].end(),
+ [&context](const epee::net_utils::network_address &na){ return context.m_remote_address == na; }), m_used_stripe_peers[index].end());
}
template<class t_payload_net_handler>
diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc
index 359dfa879..a6e12c9b3 100644
--- a/src/ringct/bulletproofs.cc
+++ b/src/ringct/bulletproofs.cc
@@ -826,7 +826,7 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs)
proof_data.reserve(proofs.size());
size_t inv_offset = 0;
std::vector<rct::key> to_invert;
- to_invert.reserve(11 * sizeof(proofs));
+ to_invert.reserve(11 * proofs.size());
size_t max_logM = 0;
for (const Bulletproof *p: proofs)
{
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 144ffe888..01735d62e 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -499,6 +499,7 @@ namespace cryptonote
res.update_available = restricted ? false : m_core.is_update_available();
res.version = restricted ? "" : MONERO_VERSION_FULL;
res.synchronized = check_core_ready();
+ res.busy_syncing = m_p2p.get_payload_object().is_busy_syncing();
res.status = CORE_RPC_STATUS_OK;
return true;
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 10f7aac7a..bb9005a5f 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -88,7 +88,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 3
-#define CORE_RPC_VERSION_MINOR 3
+#define CORE_RPC_VERSION_MINOR 4
#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)
@@ -684,6 +684,7 @@ namespace cryptonote
bool was_bootstrap_ever_used;
uint64_t database_size;
bool update_available;
+ bool busy_syncing;
std::string version;
bool synchronized;
@@ -724,6 +725,7 @@ namespace cryptonote
KV_SERIALIZE(was_bootstrap_ever_used)
KV_SERIALIZE(database_size)
KV_SERIALIZE(update_available)
+ KV_SERIALIZE(busy_syncing)
KV_SERIALIZE(version)
KV_SERIALIZE(synchronized)
END_KV_SERIALIZE_MAP()
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index a90bd3a39..2260b9c9a 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -143,6 +143,7 @@ typedef cryptonote::simple_wallet sw;
#define CREDITS_TARGET 50000
#define MAX_PAYMENT_DIFF 10000
#define MIN_PAYMENT_RATE 0.01f // per hash
+#define MAX_MNEW_ADDRESSES 1000
enum TransferType {
Transfer,
@@ -203,7 +204,7 @@ namespace
" account tag <tag_name> <account_index_1> [<account_index_2> ...]\n"
" account untag <account_index_1> [<account_index_2> ...]\n"
" account tag_description <tag_name> <description>");
- const char* USAGE_ADDRESS("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> | device [<index>] | one-off <account> <subaddress>]");
+ const char* USAGE_ADDRESS("address [ new <label text with white spaces allowed> | mnew <amount of new addresses> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> | device [<index>] | one-off <account> <subaddress>]");
const char* USAGE_INTEGRATED_ADDRESS("integrated_address [device] [<payment_id> | <address>]");
const char* USAGE_ADDRESS_BOOK("address_book [(add (<address>|<integrated address>) [<description possibly with whitespaces>])|(delete <index>)]");
const char* USAGE_SET_VARIABLE("set <option> [<value>]");
@@ -3320,7 +3321,7 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("address",
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. If \"one-off\" is specified, the address for the specified index is generated and displayed, and remembered by the wallet"));
+ 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 \"mnew\" is specified, the wallet creates as many new addresses as specified by the argument; the default label is set for the new addresses. If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text. If \"one-off\" is specified, the address for the specified index is generated and displayed, and remembered by the wallet"));
m_cmd_binder.set_handler("integrated_address",
boost::bind(&simple_wallet::on_command, this, &simple_wallet::print_integrated_address, _1),
tr(USAGE_INTEGRATED_ADDRESS),
@@ -9528,6 +9529,31 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::
print_address_sub(m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1);
m_wallet->device_show_address(m_current_subaddress_account, m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1, boost::none);
}
+ else if (local_args[0] == "mnew")
+ {
+ local_args.erase(local_args.begin());
+ if (local_args.size() != 1)
+ {
+ fail_msg_writer() << tr("Expected exactly one argument for the amount of new addresses");
+ return true;
+ }
+ uint32_t n;
+ if (!epee::string_tools::get_xtype_from_string(n, local_args[0]))
+ {
+ fail_msg_writer() << tr("failed to parse the amount of new addresses: ") << local_args[0];
+ return true;
+ }
+ if (n > MAX_MNEW_ADDRESSES)
+ {
+ fail_msg_writer() << tr("the amount of new addresses must be lower or equal to ") << MAX_MNEW_ADDRESSES;
+ return true;
+ }
+ for (uint32_t i = 0; i < n; ++i)
+ {
+ m_wallet->add_subaddress(m_current_subaddress_account, tr("(Untitled address)"));
+ print_address_sub(m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1);
+ }
+ }
else if (local_args[0] == "one-off")
{
local_args.erase(local_args.begin());