aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt9
-rw-r--r--src/blockchain_db/blockchain_db.h2
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp42
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h6
-rw-r--r--src/blockchain_utilities/CMakeLists.txt8
-rw-r--r--src/blockchain_utilities/blockchain_export.cpp8
-rw-r--r--src/blockchain_utilities/blockchain_import.cpp98
-rw-r--r--src/blockchain_utilities/blocksdat_file.cpp14
-rw-r--r--src/blockchain_utilities/blocksdat_file.h1
-rw-r--r--src/blockchain_utilities/bootstrap_file.cpp100
-rw-r--r--src/blockchain_utilities/bootstrap_file.h2
-rw-r--r--src/blocks/checkpoints.datbin44480036 -> 173732 bytes
-rw-r--r--src/checkpoints/CMakeLists.txt60
-rw-r--r--src/checkpoints/checkpoints.cpp (renamed from src/cryptonote_basic/checkpoints.cpp)11
-rw-r--r--src/checkpoints/checkpoints.h (renamed from src/cryptonote_basic/checkpoints.h)7
-rw-r--r--src/common/CMakeLists.txt1
-rw-r--r--src/common/apply_permutation.h68
-rw-r--r--src/common/base58.cpp14
-rw-r--r--src/common/command_line.h6
-rw-r--r--src/common/dns_utils.cpp70
-rw-r--r--src/common/dns_utils.h2
-rw-r--r--src/common/util.cpp91
-rw-r--r--src/common/util.h28
-rw-r--r--src/crypto/crypto.cpp125
-rw-r--r--src/crypto/crypto.h49
-rw-r--r--src/crypto/hash.h13
-rw-r--r--src/crypto/slow-hash.c4
-rw-r--r--src/cryptonote_basic/CMakeLists.txt5
-rw-r--r--src/cryptonote_basic/account.cpp2
-rw-r--r--src/cryptonote_basic/connection_context.h2
-rw-r--r--src/cryptonote_basic/cryptonote_basic.h31
-rw-r--r--src/cryptonote_basic/cryptonote_basic_impl.cpp76
-rw-r--r--src/cryptonote_basic/cryptonote_basic_impl.h55
-rw-r--r--src/cryptonote_basic/cryptonote_boost_serialization.h5
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp173
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.h24
-rw-r--r--src/cryptonote_basic/miner.cpp20
-rw-r--r--src/cryptonote_basic/subaddress_index.h102
-rw-r--r--src/cryptonote_basic/tx_extra.h14
-rw-r--r--src/cryptonote_config.h6
-rw-r--r--src/cryptonote_core/CMakeLists.txt3
-rw-r--r--src/cryptonote_core/blockchain.cpp243
-rw-r--r--src/cryptonote_core/blockchain.h11
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp29
-rw-r--r--src/cryptonote_core/cryptonote_core.h11
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp130
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.h22
-rw-r--r--src/cryptonote_core/tx_pool.cpp36
-rw-r--r--src/cryptonote_protocol/CMakeLists.txt2
-rw-r--r--src/cryptonote_protocol/block_queue.cpp5
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_defs.h6
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl44
-rw-r--r--src/daemon/CMakeLists.txt2
-rw-r--r--src/daemon/command_line_args.h5
-rw-r--r--src/daemon/command_parser_executor.cpp110
-rw-r--r--src/daemon/command_parser_executor.h2
-rw-r--r--src/daemon/command_server.cpp4
-rw-r--r--src/daemon/main.cpp9
-rw-r--r--src/daemon/rpc_command_executor.cpp135
-rw-r--r--src/daemon/rpc_command_executor.h8
-rw-r--r--src/debug_utilities/CMakeLists.txt4
-rw-r--r--src/debug_utilities/cn_deserialize.cpp2
-rw-r--r--src/debug_utilities/object_sizes.cpp3
-rw-r--r--src/mnemonics/CMakeLists.txt3
-rw-r--r--src/mnemonics/electrum-words.cpp9
-rw-r--r--src/mnemonics/lojban.h1693
-rw-r--r--src/p2p/CMakeLists.txt3
-rw-r--r--src/p2p/net_node.h6
-rw-r--r--src/p2p/net_node.inl159
-rw-r--r--src/p2p/net_peerlist.h21
-rw-r--r--src/p2p/net_peerlist_boost_serialization.h23
-rw-r--r--src/p2p/p2p_protocol_defs.h6
-rw-r--r--src/ringct/rctTypes.h2
-rw-r--r--src/rpc/CMakeLists.txt7
-rwxr-xr-x[-rw-r--r--]src/rpc/core_rpc_server.cpp208
-rw-r--r--src/rpc/core_rpc_server.h4
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h408
-rw-r--r--src/rpc/core_rpc_server_error_codes.h1
-rw-r--r--src/rpc/daemon_handler.cpp17
-rw-r--r--src/rpc/message.cpp53
-rwxr-xr-x[-rw-r--r--]src/rpc/rpc_args.cpp18
-rwxr-xr-x[-rw-r--r--]src/rpc/rpc_args.h2
-rw-r--r--src/serialization/json_object.cpp5
-rw-r--r--src/serialization/serialization.h17
-rw-r--r--src/serialization/vector.h19
-rw-r--r--src/simplewallet/CMakeLists.txt3
-rw-r--r--src/simplewallet/simplewallet.cpp1045
-rw-r--r--src/simplewallet/simplewallet.h27
-rw-r--r--src/version.cmake4
-rw-r--r--src/version.cpp.in11
-rw-r--r--src/version.h6
-rw-r--r--src/version.h.in4
-rw-r--r--src/wallet/CMakeLists.txt18
-rw-r--r--src/wallet/api/address_book.cpp24
-rw-r--r--src/wallet/api/pending_transaction.cpp16
-rw-r--r--src/wallet/api/pending_transaction.h2
-rw-r--r--src/wallet/api/subaddress.cpp91
-rw-r--r--src/wallet/api/subaddress.h56
-rw-r--r--src/wallet/api/subaddress_account.cpp90
-rw-r--r--src/wallet/api/subaddress_account.h56
-rw-r--r--src/wallet/api/transaction_history.cpp24
-rw-r--r--src/wallet/api/transaction_info.cpp17
-rw-r--r--src/wallet/api/transaction_info.h6
-rw-r--r--src/wallet/api/unsigned_transaction.cpp71
-rw-r--r--src/wallet/api/wallet.cpp287
-rw-r--r--src/wallet/api/wallet.h38
-rw-r--r--src/wallet/api/wallet_manager.cpp22
-rw-r--r--src/wallet/api/wallet_manager.h2
-rw-r--r--src/wallet/wallet2.cpp2124
-rw-r--r--src/wallet/wallet2.h336
-rw-r--r--src/wallet/wallet2_api.h143
-rw-r--r--src/wallet/wallet_args.cpp44
-rw-r--r--src/wallet/wallet_errors.h4
-rwxr-xr-x[-rw-r--r--]src/wallet/wallet_rpc_server.cpp478
-rw-r--r--src/wallet/wallet_rpc_server.h16
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h196
-rw-r--r--src/wallet/wallet_rpc_server_error_codes.h10
117 files changed, 8360 insertions, 1775 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e3cb7cfd6..6fb08b645 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -34,6 +34,11 @@ if (WIN32 OR STATIC)
add_definitions(-DMINIUPNP_STATICLIB)
endif ()
+# warnings are cleared only for GCC on Linux
+if (NOT (MINGW OR APPLE OR FREEBSD OR OPENBSD OR DRAGONFLY))
+ add_compile_options("${WARNINGS_AS_ERRORS_FLAG}") # applies only to targets that follow
+endif()
+
function (monero_private_headers group)
source_group("${group}\\Private"
FILES
@@ -96,9 +101,13 @@ function (monero_add_library name)
PRIVATE $<TARGET_PROPERTY:${name},INTERFACE_COMPILE_DEFINITIONS>)
endfunction ()
+set_source_files_properties(${CMAKE_BINARY_DIR}/version.cpp PROPERTIES GENERATED ON)
+monero_add_library(version ${CMAKE_BINARY_DIR}/version.cpp)
+
add_subdirectory(common)
add_subdirectory(crypto)
add_subdirectory(ringct)
+add_subdirectory(checkpoints)
add_subdirectory(cryptonote_basic)
add_subdirectory(cryptonote_core)
if(NOT IOS)
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 85a494ce7..838385e8a 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -711,7 +711,7 @@ public:
*
* @return true if we started the batch, false if already started
*/
- virtual bool batch_start(uint64_t batch_num_blocks=0) = 0;
+ virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0) = 0;
/**
* @brief ends a batch transaction
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index b6978bdc4..5bd02bcf7 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -548,7 +548,7 @@ bool BlockchainLMDB::need_resize(uint64_t threshold_size) const
#endif
}
-void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks)
+void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks, uint64_t batch_bytes)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
LOG_PRINT_L1("[" << __func__ << "] " << "checking DB size");
@@ -557,8 +557,8 @@ void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks)
uint64_t increase_size = 0;
if (batch_num_blocks > 0)
{
- threshold_size = get_estimated_batch_size(batch_num_blocks);
- LOG_PRINT_L1("calculated batch size: " << threshold_size);
+ threshold_size = get_estimated_batch_size(batch_num_blocks, batch_bytes);
+ MDEBUG("calculated batch size: " << threshold_size);
// The increased DB size could be a multiple of threshold_size, a fixed
// size increase (> threshold_size), or other variations.
@@ -567,7 +567,7 @@ void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks)
// minimum size increase is used to avoid frequent resizes when the batch
// size is set to a very small numbers of blocks.
increase_size = (threshold_size > min_increase_size) ? threshold_size : min_increase_size;
- LOG_PRINT_L1("increase size: " << increase_size);
+ MDEBUG("increase size: " << increase_size);
}
// if threshold_size is 0 (i.e. number of blocks for batch not passed in), it
@@ -580,7 +580,7 @@ void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks)
}
}
-uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) const
+uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks, uint64_t batch_bytes) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
uint64_t threshold_size = 0;
@@ -605,16 +605,21 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) con
block_start = block_stop - num_prev_blocks + 1;
uint32_t num_blocks_used = 0;
uint64_t total_block_size = 0;
- LOG_PRINT_L1("[" << __func__ << "] " << "m_height: " << m_height << " block_start: " << block_start << " block_stop: " << block_stop);
+ MDEBUG("[" << __func__ << "] " << "m_height: " << m_height << " block_start: " << block_start << " block_stop: " << block_stop);
size_t avg_block_size = 0;
+ if (batch_bytes)
+ {
+ avg_block_size = batch_bytes / batch_num_blocks;
+ goto estim;
+ }
if (m_height == 0)
{
- LOG_PRINT_L1("No existing blocks to check for average block size");
+ MDEBUG("No existing blocks to check for average block size");
}
else if (m_cum_count >= num_prev_blocks)
{
avg_block_size = m_cum_size / m_cum_count;
- LOG_PRINT_L1("average block size across recent " << m_cum_count << " blocks: " << avg_block_size);
+ MDEBUG("average block size across recent " << m_cum_count << " blocks: " << avg_block_size);
m_cum_size = 0;
m_cum_count = 0;
}
@@ -633,11 +638,12 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) con
}
block_rtxn_stop();
avg_block_size = total_block_size / num_blocks_used;
- LOG_PRINT_L1("average block size across recent " << num_blocks_used << " blocks: " << avg_block_size);
+ MDEBUG("average block size across recent " << num_blocks_used << " blocks: " << avg_block_size);
}
+estim:
if (avg_block_size < min_block_size)
avg_block_size = min_block_size;
- LOG_PRINT_L1("estimated average block size for batch: " << avg_block_size);
+ MDEBUG("estimated average block size for batch: " << avg_block_size);
// bigger safety margin on smaller block sizes
if (batch_fudge_factor < 5000.0)
@@ -1117,6 +1123,12 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
m_folder = filename;
+#ifdef __OpenBSD__
+ if ((mdb_flags & MDB_WRITEMAP) == 0) {
+ MCLOG_RED(el::Level::Info, "global", "Running on OpenBSD: forcing WRITEMAP");
+ mdb_flags |= MDB_WRITEMAP;
+ }
+#endif
// set up lmdb environment
if ((result = mdb_env_create(&m_env)))
throw0(DB_ERROR(lmdb_error("Failed to create lmdb environment: ", result).c_str()));
@@ -2420,8 +2432,8 @@ bool BlockchainLMDB::for_blocks_range(const uint64_t& h1, const uint64_t& h2, st
MDB_cursor_op op;
if (h1)
{
- MDB_val_set(k, h1);
- op = MDB_SET;
+ k = MDB_val{sizeof(h1), (void*)&h1};
+ op = MDB_SET;
} else
{
op = MDB_FIRST;
@@ -2540,7 +2552,7 @@ bool BlockchainLMDB::for_all_outputs(std::function<bool(uint64_t amount, const c
}
// batch_num_blocks: (optional) Used to check if resize needed before batch transaction starts.
-bool BlockchainLMDB::batch_start(uint64_t batch_num_blocks)
+bool BlockchainLMDB::batch_start(uint64_t batch_num_blocks, uint64_t batch_bytes)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
if (! m_batch_transactions)
@@ -2554,7 +2566,7 @@ bool BlockchainLMDB::batch_start(uint64_t batch_num_blocks)
check_open();
m_writer = boost::this_thread::get_id();
- check_and_resize_for_batch(batch_num_blocks);
+ check_and_resize_for_batch(batch_num_blocks, batch_bytes);
m_write_batch_txn = new mdb_txn_safe();
@@ -2927,7 +2939,7 @@ void BlockchainLMDB::get_output_key(const uint64_t &amount, const std::vector<ui
MDEBUG("Partial result: " << outputs.size() << "/" << offsets.size());
break;
}
- throw1(OUTPUT_DNE((std::string("Attempting to get output pubkey by global index (amount ") + boost::lexical_cast<std::string>(amount) + ", index " + boost::lexical_cast<std::string>(index) + ", count " + boost::lexical_cast<std::string>(get_num_outputs(amount)) + "), but key does not exist").c_str()));
+ throw1(OUTPUT_DNE((std::string("Attempting to get output pubkey by global index (amount ") + boost::lexical_cast<std::string>(amount) + ", index " + boost::lexical_cast<std::string>(index) + ", count " + boost::lexical_cast<std::string>(get_num_outputs(amount)) + "), but key does not exist (current height " + boost::lexical_cast<std::string>(height()) + ")").c_str()));
}
else if (get_result)
throw0(DB_ERROR(lmdb_error("Error attempting to retrieve an output pubkey from the db", get_result).c_str()));
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index 90274b904..98571a7f8 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -264,7 +264,7 @@ public:
);
virtual void set_batch_transactions(bool batch_transactions);
- virtual bool batch_start(uint64_t batch_num_blocks=0);
+ virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0);
virtual void batch_commit();
virtual void batch_stop();
virtual void batch_abort();
@@ -294,8 +294,8 @@ private:
void do_resize(uint64_t size_increase=0);
bool need_resize(uint64_t threshold_size=0) const;
- void check_and_resize_for_batch(uint64_t batch_num_blocks);
- uint64_t get_estimated_batch_size(uint64_t batch_num_blocks) const;
+ void check_and_resize_for_batch(uint64_t batch_num_blocks, uint64_t batch_bytes);
+ uint64_t get_estimated_batch_size(uint64_t batch_num_blocks, uint64_t batch_bytes) const;
virtual void add_block( const block& blk
, const size_t& block_size
diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt
index ffdaad4af..bd32e0c55 100644
--- a/src/blockchain_utilities/CMakeLists.txt
+++ b/src/blockchain_utilities/CMakeLists.txt
@@ -77,6 +77,7 @@ target_link_libraries(blockchain_import
cryptonote_core
blockchain_db
p2p
+ version
epee
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
@@ -89,11 +90,10 @@ if(ARCH_WIDTH)
PUBLIC -DARCH_WIDTH=${ARCH_WIDTH})
endif()
-add_dependencies(blockchain_import
- version)
set_property(TARGET blockchain_import
PROPERTY
OUTPUT_NAME "monero-blockchain-import")
+install(TARGETS blockchain_import DESTINATION bin)
monero_add_executable(blockchain_export
${blockchain_export_sources}
@@ -104,6 +104,7 @@ target_link_libraries(blockchain_export
cryptonote_core
blockchain_db
p2p
+ version
epee
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
@@ -111,9 +112,8 @@ target_link_libraries(blockchain_export
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})
-add_dependencies(blockchain_export
- version)
set_property(TARGET blockchain_export
PROPERTY
OUTPUT_NAME "monero-blockchain-export")
+install(TARGETS blockchain_export DESTINATION bin)
diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp
index 20eca09f2..60672eeda 100644
--- a/src/blockchain_utilities/blockchain_export.cpp
+++ b/src/blockchain_utilities/blockchain_export.cpp
@@ -55,7 +55,7 @@ int main(int argc, char* argv[])
uint64_t block_stop = 0;
bool blocks_dat = false;
- tools::sanitize_locale();
+ tools::on_startup();
boost::filesystem::path default_data_path {tools::get_default_data_dir()};
boost::filesystem::path default_testnet_data_path {default_data_path / "testnet"};
@@ -109,7 +109,7 @@ int main(int argc, char* argv[])
}
mlog_configure(mlog_get_default_log_path("monero-blockchain-export.log"), true);
- if (!vm["log-level"].defaulted())
+ if (!command_line::is_arg_defaulted(vm, arg_log_level))
mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str());
else
mlog_set_log(std::string(std::to_string(log_level) + ",bcutil:INFO").c_str());
@@ -178,7 +178,7 @@ int main(int argc, char* argv[])
}
r = core_storage->init(db, opt_testnet);
- CHECK_AND_ASSERT_MES(r, false, "Failed to initialize source blockchain storage");
+ CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
LOG_PRINT_L0("Source blockchain storage initialized OK");
LOG_PRINT_L0("Exporting blockchain raw data...");
@@ -192,7 +192,7 @@ int main(int argc, char* argv[])
BootstrapFile bootstrap;
r = bootstrap.store_blockchain_raw(core_storage, NULL, output_file_path, block_stop);
}
- CHECK_AND_ASSERT_MES(r, false, "Failed to export blockchain raw data");
+ CHECK_AND_ASSERT_MES(r, 1, "Failed to export blockchain raw data");
LOG_PRINT_L0("Blockchain raw data exported OK");
return 0;
diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp
index 635a70b10..2a956dbdb 100644
--- a/src/blockchain_utilities/blockchain_import.cpp
+++ b/src/blockchain_utilities/blockchain_import.cpp
@@ -169,6 +169,26 @@ int check_flush(cryptonote::core &core, std::list<block_complete_entry> &blocks,
if (!force && blocks.size() < db_batch_size)
return 0;
+ // wait till we can verify a full HOH without extra, for speed
+ uint64_t new_height = core.get_blockchain_storage().get_db().height() + blocks.size();
+ if (!force && new_height % HASH_OF_HASHES_STEP)
+ return 0;
+
+ std::list<crypto::hash> hashes;
+ for (const auto &b: blocks)
+ {
+ cryptonote::block block;
+ if (!parse_and_validate_block_from_blob(b.block, block))
+ {
+ MERROR("Failed to parse block: "
+ << epee::string_tools::pod_to_hex(get_blob_hash(b.block)));
+ core.cleanup_handle_incoming_blocks();
+ return 1;
+ }
+ hashes.push_back(cryptonote::get_block_hash(block));
+ }
+ core.prevalidate_block_hashes(core.get_blockchain_storage().get_db().height(), hashes);
+
core.prepare_handle_incoming_blocks(blocks);
for(const block_complete_entry& block_entry: blocks)
@@ -230,11 +250,22 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
return false;
}
+ uint64_t start_height = 1, seek_height;
+ if (opt_resume)
+ start_height = core.get_blockchain_storage().get_current_blockchain_height();
+
+ seek_height = start_height;
BootstrapFile bootstrap;
+ streampos pos;
// BootstrapFile bootstrap(import_file_path);
- uint64_t total_source_blocks = bootstrap.count_blocks(import_file_path);
+ uint64_t total_source_blocks = bootstrap.count_blocks(import_file_path, pos, seek_height);
MINFO("bootstrap file last block number: " << total_source_blocks-1 << " (zero-based height) total blocks: " << total_source_blocks);
+ if (total_source_blocks-1 <= start_height)
+ {
+ return false;
+ }
+
std::cout << ENDL;
std::cout << "Preparing to read blocks..." << ENDL;
std::cout << ENDL;
@@ -259,11 +290,7 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
block b;
transaction tx;
int quit = 0;
- uint64_t bytes_read = 0;
-
- uint64_t start_height = 1;
- if (opt_resume)
- start_height = core.get_blockchain_storage().get_current_blockchain_height();
+ uint64_t bytes_read;
// Note that a new blockchain will start with block number 0 (total blocks: 1)
// due to genesis block being added at initialization.
@@ -280,18 +307,35 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
bool use_batch = opt_batch && !opt_verify;
- if (use_batch)
- core.get_blockchain_storage().get_db().batch_start(db_batch_size);
-
MINFO("Reading blockchain from bootstrap file...");
std::cout << ENDL;
std::list<block_complete_entry> blocks;
- // Within the loop, we skip to start_height before we start adding.
- // TODO: Not a bottleneck, but we can use what's done in count_blocks() and
- // only do the chunk size reads, skipping the chunk content reads until we're
- // at start_height.
+ // Skip to start_height before we start adding.
+ {
+ bool q2 = false;
+ import_file.seekg(pos);
+ bytes_read = bootstrap.count_bytes(import_file, start_height-seek_height, h, q2);
+ if (q2)
+ {
+ quit = 2;
+ goto quitting;
+ }
+ h = start_height;
+ }
+
+ if (use_batch)
+ {
+ uint64_t bytes, h2;
+ bool q2;
+ pos = import_file.tellg();
+ bytes = bootstrap.count_bytes(import_file, db_batch_size, h2, q2);
+ if (import_file.eof())
+ import_file.clear();
+ import_file.seekg(pos);
+ core.get_blockchain_storage().get_db().batch_start(db_batch_size, bytes);
+ }
while (! quit)
{
uint32_t chunk_size;
@@ -344,11 +388,6 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
bytes_read += chunk_size;
MDEBUG("Total bytes read: " << bytes_read);
- if (h + NUM_BLOCKS_PER_CHUNK < start_height + 1)
- {
- h += NUM_BLOCKS_PER_CHUNK;
- continue;
- }
if (h > block_stop)
{
std::cout << refresh_string << "block " << h-1
@@ -456,11 +495,16 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
{
if ((h-1) % db_batch_size == 0)
{
+ uint64_t bytes, h2;
+ bool q2;
std::cout << refresh_string;
// zero-based height
std::cout << ENDL << "[- batch commit at height " << h-1 << " -]" << ENDL;
core.get_blockchain_storage().get_db().batch_stop();
- core.get_blockchain_storage().get_db().batch_start(db_batch_size);
+ pos = import_file.tellg();
+ bytes = bootstrap.count_bytes(import_file, db_batch_size, h2, q2);
+ import_file.seekg(pos);
+ core.get_blockchain_storage().get_db().batch_start(db_batch_size, bytes);
std::cout << ENDL;
core.get_blockchain_storage().get_db().show_stats();
}
@@ -477,6 +521,7 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
}
} // while
+quitting:
import_file.close();
if (opt_verify)
@@ -526,7 +571,7 @@ int main(int argc, char* argv[])
std::string m_config_folder;
std::string db_arg_str;
- tools::sanitize_locale();
+ tools::on_startup();
boost::filesystem::path default_data_path {tools::get_default_data_dir()};
boost::filesystem::path default_testnet_data_path {default_data_path / "testnet"};
@@ -560,10 +605,7 @@ int main(int argc, char* argv[])
const command_line::arg_descriptor<bool> arg_resume = {"resume",
"Resume from current height if output database already exists", true};
- //command_line::add_arg(desc_cmd_sett, command_line::arg_data_dir, default_data_path.string());
- //command_line::add_arg(desc_cmd_sett, command_line::arg_testnet_data_dir, default_testnet_data_path.string());
command_line::add_arg(desc_cmd_sett, arg_input_file);
- //command_line::add_arg(desc_cmd_sett, arg_testnet_on);
command_line::add_arg(desc_cmd_sett, arg_log_level);
command_line::add_arg(desc_cmd_sett, arg_database);
command_line::add_arg(desc_cmd_sett, arg_batch_size);
@@ -609,7 +651,7 @@ int main(int argc, char* argv[])
return 1;
}
- if (! opt_batch && ! vm["batch-size"].defaulted())
+ if (! opt_batch && !command_line::is_arg_defaulted(vm, arg_batch_size))
{
std::cerr << "Error: batch-size set, but batch option not enabled" << ENDL;
return 1;
@@ -619,7 +661,7 @@ int main(int argc, char* argv[])
std::cerr << "Error: batch-size must be > 0" << ENDL;
return 1;
}
- if (opt_verify && vm["batch-size"].defaulted())
+ if (opt_verify && command_line::is_arg_defaulted(vm, arg_batch_size))
{
// usually want batch size default lower if verify on, so progress can be
// frequently saved.
@@ -638,7 +680,7 @@ int main(int argc, char* argv[])
db_arg_str = command_line::get_arg(vm, arg_database);
mlog_configure(mlog_get_default_log_path("monero-blockchain-import.log"), true);
- if (!vm["log-level"].defaulted())
+ if (!command_line::is_arg_defaulted(vm, arg_log_level))
mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str());
else
mlog_set_log(std::string(std::to_string(log_level) + ",bcutil:INFO").c_str());
@@ -710,7 +752,7 @@ int main(int argc, char* argv[])
}
core.get_blockchain_storage().get_db().set_batch_transactions(true);
- if (! vm["pop-blocks"].defaulted())
+ if (!command_line::is_arg_defaulted(vm, arg_pop_blocks))
{
num_blocks = command_line::get_arg(vm, arg_pop_blocks);
MINFO("height: " << core.get_blockchain_storage().get_current_blockchain_height());
@@ -719,7 +761,7 @@ int main(int argc, char* argv[])
return 0;
}
- if (! vm["drop-hard-fork"].defaulted())
+ if (!command_line::is_arg_defaulted(vm, arg_drop_hf))
{
MINFO("Dropping hard fork tables...");
core.get_blockchain_storage().get_db().drop_hard_fork_info();
diff --git a/src/blockchain_utilities/blocksdat_file.cpp b/src/blockchain_utilities/blocksdat_file.cpp
index 63224225f..32e93345e 100644
--- a/src/blockchain_utilities/blocksdat_file.cpp
+++ b/src/blockchain_utilities/blocksdat_file.cpp
@@ -82,7 +82,7 @@ bool BlocksdatFile::open_writer(const boost::filesystem::path& file_path, uint64
bool BlocksdatFile::initialize_file(uint64_t block_stop)
{
- const uint32_t nblocks = block_stop + 1;
+ const uint32_t nblocks = (block_stop + 1) / HASH_OF_HASHES_STEP;
unsigned char nblocksc[4];
nblocksc[0] = nblocks & 0xff;
@@ -101,8 +101,16 @@ bool BlocksdatFile::initialize_file(uint64_t block_stop)
void BlocksdatFile::write_block(const crypto::hash& block_hash)
{
- const std::string data(block_hash.data, sizeof(block_hash));
- *m_raw_data_file << data;
+ m_hashes.push_back(block_hash);
+ while (m_hashes.size() >= HASH_OF_HASHES_STEP)
+ {
+ crypto::hash hash;
+ crypto::cn_fast_hash(m_hashes.data(), HASH_OF_HASHES_STEP * sizeof(crypto::hash), hash);
+ memmove(m_hashes.data(), m_hashes.data() + HASH_OF_HASHES_STEP * sizeof(crypto::hash), (m_hashes.size() - HASH_OF_HASHES_STEP) * sizeof(crypto::hash));
+ m_hashes.resize(m_hashes.size() - HASH_OF_HASHES_STEP);
+ const std::string data(hash.data, sizeof(hash));
+ *m_raw_data_file << data;
+ }
}
bool BlocksdatFile::close()
diff --git a/src/blockchain_utilities/blocksdat_file.h b/src/blockchain_utilities/blocksdat_file.h
index 0a5913058..d43811772 100644
--- a/src/blockchain_utilities/blocksdat_file.h
+++ b/src/blockchain_utilities/blocksdat_file.h
@@ -76,4 +76,5 @@ protected:
private:
uint64_t m_cur_height; // tracks current height during export
+ std::vector<crypto::hash> m_hashes;
};
diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp
index 2b1a5d6c7..a004d3547 100644
--- a/src/blockchain_utilities/bootstrap_file.cpp
+++ b/src/blockchain_utilities/bootstrap_file.cpp
@@ -375,39 +375,15 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file)
return full_header_size;
}
-uint64_t BootstrapFile::count_blocks(const std::string& import_file_path)
+uint64_t BootstrapFile::count_bytes(std::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit)
{
- boost::filesystem::path raw_file_path(import_file_path);
- boost::system::error_code ec;
- if (!boost::filesystem::exists(raw_file_path, ec))
- {
- MFATAL("bootstrap file not found: " << raw_file_path);
- throw std::runtime_error("Aborting");
- }
- std::ifstream import_file;
- import_file.open(import_file_path, std::ios_base::binary | std::ifstream::in);
-
- uint64_t h = 0;
- if (import_file.fail())
- {
- MFATAL("import_file.open() fail");
- throw std::runtime_error("Aborting");
- }
-
- uint64_t full_header_size; // 4 byte magic + length of header structures
- full_header_size = seek_to_first_chunk(import_file);
-
- MINFO("Scanning blockchain from bootstrap file...");
- block b;
- bool quit = false;
uint64_t bytes_read = 0;
- int progress_interval = 10;
-
+ uint32_t chunk_size;
+ char buf1[sizeof(chunk_size)];
std::string str1;
- char buf1[2048];
- while (! quit)
+ h = 0;
+ while (1)
{
- uint32_t chunk_size;
import_file.read(buf1, sizeof(chunk_size));
if (!import_file) {
std::cout << refresh_string;
@@ -415,15 +391,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path)
quit = true;
break;
}
- h += NUM_BLOCKS_PER_CHUNK;
- if ((h-1) % progress_interval == 0)
- {
- std::cout << "\r" << "block height: " << h-1 <<
- " " <<
- std::flush;
- }
bytes_read += sizeof(chunk_size);
-
str1.assign(buf1, sizeof(chunk_size));
if (! ::serialization::parse_binary(str1, chunk_size))
throw std::runtime_error("Error in deserialization of chunk_size");
@@ -456,6 +424,64 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path)
throw std::runtime_error("Aborting");
}
bytes_read += chunk_size;
+ h += NUM_BLOCKS_PER_CHUNK;
+ if (h >= blocks)
+ break;
+ }
+ return bytes_read;
+}
+
+uint64_t BootstrapFile::count_blocks(const std::string& import_file_path)
+{
+ streampos dummy_pos;
+ uint64_t dummy_height = 0;
+ return count_blocks(import_file_path, dummy_pos, dummy_height);
+}
+
+// If seek_height is non-zero on entry, return a stream position <= this height when finished.
+// And return the actual height corresponding to this position. Allows the caller to locate its
+// starting position without having to reread the entire file again.
+uint64_t BootstrapFile::count_blocks(const std::string& import_file_path, streampos &start_pos, uint64_t& seek_height)
+{
+ boost::filesystem::path raw_file_path(import_file_path);
+ boost::system::error_code ec;
+ if (!boost::filesystem::exists(raw_file_path, ec))
+ {
+ MFATAL("bootstrap file not found: " << raw_file_path);
+ throw std::runtime_error("Aborting");
+ }
+ std::ifstream import_file;
+ import_file.open(import_file_path, std::ios_base::binary | std::ifstream::in);
+
+ uint64_t start_height = seek_height;
+ uint64_t h = 0;
+ if (import_file.fail())
+ {
+ MFATAL("import_file.open() fail");
+ throw std::runtime_error("Aborting");
+ }
+
+ uint64_t full_header_size; // 4 byte magic + length of header structures
+ full_header_size = seek_to_first_chunk(import_file);
+
+ MINFO("Scanning blockchain from bootstrap file...");
+ bool quit = false;
+ uint64_t bytes_read = 0, blocks;
+ int progress_interval = 10;
+
+ while (! quit)
+ {
+ if (start_height && h + progress_interval >= start_height - 1)
+ {
+ start_height = 0;
+ start_pos = import_file.tellg();
+ seek_height = h;
+ }
+ bytes_read += count_bytes(import_file, progress_interval, blocks, quit);
+ h += blocks;
+ std::cout << "\r" << "block height: " << h-1 <<
+ " " <<
+ std::flush;
// std::cout << refresh_string;
MDEBUG("Number bytes scanned: " << bytes_read);
diff --git a/src/blockchain_utilities/bootstrap_file.h b/src/blockchain_utilities/bootstrap_file.h
index 1a646b54b..c3969a357 100644
--- a/src/blockchain_utilities/bootstrap_file.h
+++ b/src/blockchain_utilities/bootstrap_file.h
@@ -56,6 +56,8 @@ class BootstrapFile
{
public:
+ uint64_t count_bytes(std::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit);
+ uint64_t count_blocks(const std::string& dir_path, streampos& start_pos, uint64_t& seek_height);
uint64_t count_blocks(const std::string& dir_path);
uint64_t seek_to_first_chunk(std::ifstream& import_file);
diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat
index 15fa042cf..c12c3d8a0 100644
--- a/src/blocks/checkpoints.dat
+++ b/src/blocks/checkpoints.dat
Binary files differ
diff --git a/src/checkpoints/CMakeLists.txt b/src/checkpoints/CMakeLists.txt
new file mode 100644
index 000000000..bc7a27e36
--- /dev/null
+++ b/src/checkpoints/CMakeLists.txt
@@ -0,0 +1,60 @@
+# Copyright (c) 2014-2017, The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+if(APPLE)
+ find_library(IOKIT_LIBRARY IOKit)
+ mark_as_advanced(IOKIT_LIBRARY)
+ list(APPEND EXTRA_LIBRARIES ${IOKIT_LIBRARY})
+endif()
+
+set(checkpoints_sources
+ checkpoints.cpp)
+
+set(checkpoints_headers)
+
+set(checkpoints_private_headers
+ checkpoints.h)
+
+monero_private_headers(checkpoints
+ ${checkpoints_private_headers})
+monero_add_library(checkpoints
+ ${checkpoints_sources}
+ ${checkpoints_headers}
+ ${checkpoints_private_headers})
+target_link_libraries(checkpoints
+ PUBLIC
+ common
+ cncrypto
+ ${Boost_DATE_TIME_LIBRARY}
+ ${Boost_PROGRAM_OPTIONS_LIBRARY}
+ ${Boost_SERIALIZATION_LIBRARY}
+ ${Boost_FILESYSTEM_LIBRARY}
+ ${Boost_SYSTEM_LIBRARY}
+ ${Boost_THREAD_LIBRARY}
+ PRIVATE
+ ${EXTRA_LIBRARIES})
diff --git a/src/cryptonote_basic/checkpoints.cpp b/src/checkpoints/checkpoints.cpp
index 98e509561..bea392db0 100644
--- a/src/cryptonote_basic/checkpoints.cpp
+++ b/src/checkpoints/checkpoints.cpp
@@ -36,6 +36,7 @@ using namespace epee;
#include "common/dns_utils.h"
#include "include_base_utils.h"
+#include "storages/portable_storage_template_helper.h" // epee json include
#include <sstream>
#include <random>
@@ -51,7 +52,7 @@ namespace cryptonote
//---------------------------------------------------------------------------
bool checkpoints::add_checkpoint(uint64_t height, const std::string& hash_str)
{
- crypto::hash h = null_hash;
+ crypto::hash h = crypto::null_hash;
bool r = epee::string_tools::parse_tpod_from_hex_string(hash_str, h);
CHECK_AND_ASSERT_MES(r, false, "Failed to parse checkpoint hash string into binary representation!");
@@ -135,8 +136,14 @@ namespace cryptonote
return true;
}
- bool checkpoints::init_default_checkpoints()
+ bool checkpoints::init_default_checkpoints(bool testnet)
{
+ if (testnet)
+ {
+ // just use the genesis block on testnet
+ ADD_CHECKPOINT(0, "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b");
+ return true;
+ }
ADD_CHECKPOINT(1, "771fbcd656ec1464d3a02ead5e18644030007a0fc664c0a964d30922821a8148");
ADD_CHECKPOINT(10, "c0e3b387e47042f72d8ccdca88071ff96bff1ac7cde09ae113dbb7ad3fe92381");
ADD_CHECKPOINT(100, "ac3e11ca545e57c49fca2b4e8c48c03c23be047c43e471e1394528b1f9f80b2d");
diff --git a/src/cryptonote_basic/checkpoints.h b/src/checkpoints/checkpoints.h
index 3a74d8a69..a643c5790 100644
--- a/src/cryptonote_basic/checkpoints.h
+++ b/src/checkpoints/checkpoints.h
@@ -31,9 +31,9 @@
#pragma once
#include <map>
#include <vector>
-#include "cryptonote_basic_impl.h"
#include "misc_log_ex.h"
-#include "storages/portable_storage_template_helper.h" // epee json include
+#include "crypto/hash.h"
+#include "serialization/keyvalue_serialization.h"
#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false);
#define JSON_HASH_FILE_NAME "checkpoints.json"
@@ -148,10 +148,11 @@ namespace cryptonote
/**
* @brief loads the default main chain checkpoints
+ * @param testnet whether to load testnet checkpoints or mainnet
*
* @return true unless adding a checkpoint fails
*/
- bool init_default_checkpoints();
+ bool init_default_checkpoints(bool testnet);
/**
* @brief load new checkpoints
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 19d90253b..50887e35c 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -47,6 +47,7 @@ endif()
set(common_headers)
set(common_private_headers
+ apply_permutation.h
base58.h
boost_serialization_helper.h
command_line.h
diff --git a/src/common/apply_permutation.h b/src/common/apply_permutation.h
new file mode 100644
index 000000000..4fd952686
--- /dev/null
+++ b/src/common/apply_permutation.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2017, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Most of this file is originally copyright (c) 2017 Raymond Chen, Microsoft
+// This algorithm is adapted from Raymond Chen's code:
+// https://blogs.msdn.microsoft.com/oldnewthing/20170109-00/?p=95145
+
+#include <vector>
+#include <functional>
+#include "misc_log_ex.h"
+
+namespace tools
+{
+
+template<typename F>
+void apply_permutation(std::vector<size_t> permutation, const F &swap)
+{
+ //sanity check
+ for (size_t n = 0; n < permutation.size(); ++n)
+ CHECK_AND_ASSERT_THROW_MES(std::find(permutation.begin(), permutation.end(), n) != permutation.end(), "Bad permutation");
+
+ for (size_t i = 0; i < permutation.size(); ++i)
+ {
+ size_t current = i;
+ while (i != permutation[current])
+ {
+ size_t next = permutation[current];
+ swap(current, next);
+ permutation[current] = current;
+ current = next;
+ }
+ permutation[current] = current;
+ }
+}
+
+template<typename T>
+void apply_permutation(const std::vector<size_t> &permutation, std::vector<T> &v)
+{
+ CHECK_AND_ASSERT_THROW_MES(permutation.size() == v.size(), "Mismatched vector sizes");
+ apply_permutation(permutation, [&v](size_t i0, size_t i1){ std::swap(v[i0], v[i1]); });
+}
+
+}
diff --git a/src/common/base58.cpp b/src/common/base58.cpp
index 64cb7c0de..941373443 100644
--- a/src/common/base58.cpp
+++ b/src/common/base58.cpp
@@ -111,13 +111,13 @@ namespace tools
uint64_t res = 0;
switch (9 - size)
{
- case 1: res |= *data++;
- case 2: res <<= 8; res |= *data++;
- case 3: res <<= 8; res |= *data++;
- case 4: res <<= 8; res |= *data++;
- case 5: res <<= 8; res |= *data++;
- case 6: res <<= 8; res |= *data++;
- case 7: res <<= 8; res |= *data++;
+ case 1: res |= *data++; /* FALLTHRU */
+ case 2: res <<= 8; res |= *data++; /* FALLTHRU */
+ case 3: res <<= 8; res |= *data++; /* FALLTHRU */
+ case 4: res <<= 8; res |= *data++; /* FALLTHRU */
+ case 5: res <<= 8; res |= *data++; /* FALLTHRU */
+ case 6: res <<= 8; res |= *data++; /* FALLTHRU */
+ case 7: res <<= 8; res |= *data++; /* FALLTHRU */
case 8: res <<= 8; res |= *data; break;
default: assert(false);
}
diff --git a/src/common/command_line.h b/src/common/command_line.h
index d4231acd0..04fd70be5 100644
--- a/src/common/command_line.h
+++ b/src/common/command_line.h
@@ -189,6 +189,12 @@ namespace command_line
return !value.empty();
}
+ template<typename T, bool required>
+ bool is_arg_defaulted(const boost::program_options::variables_map& vm, const arg_descriptor<T, required>& arg)
+ {
+ return vm[arg.name].defaulted();
+ }
+
template<typename T, bool required>
T get_arg(const boost::program_options::variables_map& vm, const arg_descriptor<T, required>& arg)
diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp
index e7ff11c5c..f549218cb 100644
--- a/src/common/dns_utils.cpp
+++ b/src/common/dns_utils.cpp
@@ -27,8 +27,6 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "common/dns_utils.h"
-#include "common/i18n.h"
-#include "cryptonote_basic/cryptonote_basic_impl.h"
// check local first (in the event of static or in-source compilation of libunbound)
#include "unbound.h"
@@ -42,6 +40,8 @@ namespace bf = boost::filesystem;
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "net.dns"
+#define DEFAULT_DNS_PUBLIC_ADDR "8.8.4.4"
+
static boost::mutex instance_lock;
namespace
@@ -199,16 +199,19 @@ public:
DNSResolver::DNSResolver() : m_data(new DNSResolverData())
{
int use_dns_public = 0;
- const char* dns_public_addr = "8.8.4.4";
+ std::string dns_public_addr = DEFAULT_DNS_PUBLIC_ADDR;
if (auto res = getenv("DNS_PUBLIC"))
{
- std::string dns_public(res);
- // TODO: could allow parsing of IP and protocol: e.g. DNS_PUBLIC=tcp:8.8.8.8
- if (dns_public == "tcp")
+ dns_public_addr = tools::dns_utils::parse_dns_public(res);
+ if (!dns_public_addr.empty())
{
- LOG_PRINT_L0("Using public DNS server: " << dns_public_addr << " (TCP)");
+ MGINFO("Using public DNS server: " << dns_public_addr << " (TCP)");
use_dns_public = 1;
}
+ else
+ {
+ MERROR("Failed to parse DNS_PUBLIC");
+ }
}
// init libunbound context
@@ -216,7 +219,7 @@ DNSResolver::DNSResolver() : m_data(new DNSResolverData())
if (use_dns_public)
{
- ub_ctx_set_fwd(m_data->m_ub_context, string_copy(dns_public_addr));
+ ub_ctx_set_fwd(m_data->m_ub_context, dns_public_addr.c_str());
ub_ctx_set_option(m_data->m_ub_context, string_copy("do-udp:"), string_copy("no"));
ub_ctx_set_option(m_data->m_ub_context, string_copy("do-tcp:"), string_copy("yes"));
}
@@ -326,8 +329,6 @@ bool DNSResolver::check_address_syntax(const char *addr) const
namespace dns_utils
{
-const char *tr(const char *str) { return i18n_translate(str, "tools::dns_utils"); }
-
//-----------------------------------------------------------------------
// TODO: parse the string in a less stupid way, probably with regex
std::string address_from_txt_record(const std::string& s)
@@ -447,19 +448,28 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std
std::uniform_int_distribution<int> dis(0, dns_urls.size() - 1);
size_t first_index = dis(gen);
- bool avail, valid;
+ // send all requests in parallel
+ std::vector<boost::thread> threads(dns_urls.size());
+ std::deque<bool> avail(dns_urls.size(), false), valid(dns_urls.size(), false);
+ for (size_t n = 0; n < dns_urls.size(); ++n)
+ {
+ threads[n] = boost::thread([n, dns_urls, &records, &avail, &valid](){
+ records[n] = tools::DNSResolver::instance().get_txt_record(dns_urls[n], avail[n], valid[n]);
+ });
+ }
+ for (size_t n = 0; n < dns_urls.size(); ++n)
+ threads[n].join();
+
size_t cur_index = first_index;
do
{
- std::string url = dns_urls[cur_index];
-
- records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid);
- if (!avail)
+ const std::string &url = dns_urls[cur_index];
+ if (!avail[cur_index])
{
records[cur_index].clear();
LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping.");
}
- if (!valid)
+ if (!valid[cur_index])
{
records[cur_index].clear();
LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping.");
@@ -514,6 +524,34 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std
return true;
}
+std::string parse_dns_public(const char *s)
+{
+ unsigned ip0, ip1, ip2, ip3;
+ char c;
+ std::string dns_public_addr;
+ if (!strcmp(s, "tcp"))
+ {
+ dns_public_addr = DEFAULT_DNS_PUBLIC_ADDR;
+ LOG_PRINT_L0("Using default public DNS server: " << dns_public_addr << " (TCP)");
+ }
+ else if (sscanf(s, "tcp://%u.%u.%u.%u%c", &ip0, &ip1, &ip2, &ip3, &c) == 4)
+ {
+ if (ip0 > 255 || ip1 > 255 || ip2 > 255 || ip3 > 255)
+ {
+ MERROR("Invalid IP: " << s << ", using default");
+ }
+ else
+ {
+ dns_public_addr = std::string(s + strlen("tcp://"));
+ }
+ }
+ else
+ {
+ MERROR("Invalid DNS_PUBLIC contents, ignored");
+ }
+ return dns_public_addr;
+}
+
} // namespace tools::dns_utils
} // namespace tools
diff --git a/src/common/dns_utils.h b/src/common/dns_utils.h
index f19584516..c0a2dbf2b 100644
--- a/src/common/dns_utils.h
+++ b/src/common/dns_utils.h
@@ -167,6 +167,8 @@ std::string get_account_address_as_str_from_url(const std::string& url, bool& dn
bool load_txt_records_from_dns(std::vector<std::string> &records, const std::vector<std::string> &dns_urls);
+std::string parse_dns_public(const char *s);
+
} // namespace tools::dns_utils
} // namespace tools
diff --git a/src/common/util.cpp b/src/common/util.cpp
index 046961b06..30746f680 100644
--- a/src/common/util.cpp
+++ b/src/common/util.cpp
@@ -30,6 +30,10 @@
#include <cstdio>
+#ifdef __GLIBC__
+#include <gnu/libc-version.h>
+#endif
+
#include "include_base_utils.h"
#include "file_io_utils.h"
using namespace epee;
@@ -39,11 +43,13 @@ using namespace epee;
#include "net/http_client.h" // epee::net_utils::...
#ifdef WIN32
-#include <windows.h>
-#include <shlobj.h>
-#include <strsafe.h>
+ #include <windows.h>
+ #include <shlobj.h>
+ #include <strsafe.h>
#else
-#include <sys/utsname.h>
+ #include <sys/file.h>
+ #include <sys/utsname.h>
+ #include <sys/stat.h>
#endif
#include <boost/filesystem.hpp>
#include <boost/asio.hpp>
@@ -53,7 +59,12 @@ namespace tools
{
std::function<void(int)> signal_handler::m_handler;
- std::unique_ptr<std::FILE, tools::close_file> create_private_file(const std::string& name)
+ private_file::private_file() noexcept : m_handle(), m_filename() {}
+
+ private_file::private_file(std::FILE* handle, std::string&& filename) noexcept
+ : m_handle(handle), m_filename(std::move(filename)) {}
+
+ private_file private_file::create(std::string name)
{
#ifdef WIN32
struct close_handle
@@ -70,17 +81,17 @@ namespace tools
const bool fail = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, std::addressof(temp)) == 0;
process.reset(temp);
if (fail)
- return nullptr;
+ return {};
}
DWORD sid_size = 0;
GetTokenInformation(process.get(), TokenOwner, nullptr, 0, std::addressof(sid_size));
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
- return nullptr;
+ return {};
std::unique_ptr<char[]> sid{new char[sid_size]};
if (!GetTokenInformation(process.get(), TokenOwner, sid.get(), sid_size, std::addressof(sid_size)))
- return nullptr;
+ return {};
const PSID psid = reinterpret_cast<const PTOKEN_OWNER>(sid.get())->Owner;
const DWORD daclSize =
@@ -88,17 +99,17 @@ namespace tools
const std::unique_ptr<char[]> dacl{new char[daclSize]};
if (!InitializeAcl(reinterpret_cast<PACL>(dacl.get()), daclSize, ACL_REVISION))
- return nullptr;
+ return {};
if (!AddAccessAllowedAce(reinterpret_cast<PACL>(dacl.get()), ACL_REVISION, (READ_CONTROL | FILE_GENERIC_READ | DELETE), psid))
- return nullptr;
+ return {};
SECURITY_DESCRIPTOR descriptor{};
if (!InitializeSecurityDescriptor(std::addressof(descriptor), SECURITY_DESCRIPTOR_REVISION))
- return nullptr;
+ return {};
if (!SetSecurityDescriptorDacl(std::addressof(descriptor), true, reinterpret_cast<PACL>(dacl.get()), false))
- return nullptr;
+ return {};
SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES), std::addressof(descriptor), false};
std::unique_ptr<void, close_handle> file{
@@ -106,7 +117,7 @@ namespace tools
name.c_str(),
GENERIC_WRITE, FILE_SHARE_READ,
std::addressof(attributes),
- CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY,
+ CREATE_NEW, (FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE),
nullptr
)
};
@@ -121,22 +132,49 @@ namespace tools
{
_close(fd);
}
- return {real_file, tools::close_file{}};
+ return {real_file, std::move(name)};
}
}
#else
- const int fd = open(name.c_str(), (O_RDWR | O_EXCL | O_CREAT), S_IRUSR);
- if (0 <= fd)
+ const int fdr = open(name.c_str(), (O_RDONLY | O_CREAT), S_IRUSR);
+ if (0 <= fdr)
{
- std::FILE* file = fdopen(fd, "w");
- if (!file)
+ struct stat rstats = {};
+ if (fstat(fdr, std::addressof(rstats)) != 0)
+ {
+ close(fdr);
+ return {};
+ }
+ fchmod(fdr, (S_IRUSR | S_IWUSR));
+ const int fdw = open(name.c_str(), O_RDWR);
+ fchmod(fdr, rstats.st_mode);
+ close(fdr);
+
+ if (0 <= fdw)
{
- close(fd);
+ struct stat wstats = {};
+ if (fstat(fdw, std::addressof(wstats)) == 0 &&
+ rstats.st_dev == wstats.st_dev && rstats.st_ino == wstats.st_ino &&
+ flock(fdw, (LOCK_EX | LOCK_NB)) == 0 && ftruncate(fdw, 0) == 0)
+ {
+ std::FILE* file = fdopen(fdw, "w");
+ if (file) return {file, std::move(name)};
+ }
+ close(fdw);
}
- return {file, tools::close_file{}};
}
#endif
- return nullptr;
+ return {};
+ }
+
+ private_file::~private_file() noexcept
+ {
+ try
+ {
+ boost::system::error_code ec{};
+ boost::filesystem::remove(filename(), ec);
+ }
+ catch (...) {}
}
#ifdef WIN32
@@ -502,6 +540,17 @@ std::string get_nix_version_display_string()
}
return false;
}
+ bool on_startup()
+ {
+ sanitize_locale();
+
+#ifdef __GLIBC__
+ const char *ver = gnu_get_libc_version();
+ if (!strcmp(ver, "2.25"))
+ MCLOG_RED(el::Level::Warning, "global", "Running with glibc " << ver << ", hangs may occur - change glibc version if possible");
+#endif
+ return true;
+ }
void set_strict_default_file_permissions(bool strict)
{
#if defined(__MINGW32__) || defined(__MINGW__)
diff --git a/src/common/util.h b/src/common/util.h
index 2452bc9d5..2e4d6e917 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -60,8 +60,30 @@ namespace tools
}
};
- //! \return File only readable by owner. nullptr if `filename` exists.
- std::unique_ptr<std::FILE, close_file> create_private_file(const std::string& filename);
+ //! A file restricted to process owner AND process. Deletes file on destruction.
+ class private_file {
+ std::unique_ptr<std::FILE, close_file> m_handle;
+ std::string m_filename;
+
+ private_file(std::FILE* handle, std::string&& filename) noexcept;
+ public:
+
+ //! `handle() == nullptr && filename.empty()`.
+ private_file() noexcept;
+
+ /*! \return File only readable by owner and only used by this process
+ OR `private_file{}` on error. */
+ static private_file create(std::string filename);
+
+ private_file(private_file&&) = default;
+ private_file& operator=(private_file&&) = default;
+
+ //! Deletes `filename()` and closes `handle()`.
+ ~private_file() noexcept;
+
+ std::FILE* handle() const noexcept { return m_handle.get(); }
+ const std::string& filename() const noexcept { return m_filename; }
+ };
/*! \brief Returns the default data directory.
*
@@ -107,6 +129,8 @@ namespace tools
bool sanitize_locale();
+ bool on_startup();
+
/*! \brief Defines a signal handler for win32 and *nix
*/
class signal_handler
diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp
index 5fb670f87..95ba34828 100644
--- a/src/crypto/crypto.cpp
+++ b/src/crypto/crypto.cpp
@@ -87,7 +87,7 @@ namespace crypto {
random_scalar_not_thread_safe(res);
}
- static inline void hash_to_scalar(const void *data, size_t length, ec_scalar &res) {
+ void hash_to_scalar(const void *data, size_t length, ec_scalar &res) {
cn_fast_hash(data, length, reinterpret_cast<hash &>(res));
sc_reduce32(&res);
}
@@ -189,6 +189,25 @@ namespace crypto {
sc_add(&derived_key, &base, &scalar);
}
+ bool crypto_ops::derive_subaddress_public_key(const public_key &out_key, const key_derivation &derivation, std::size_t output_index, public_key &derived_key) {
+ ec_scalar scalar;
+ ge_p3 point1;
+ ge_p3 point2;
+ ge_cached point3;
+ ge_p1p1 point4;
+ ge_p2 point5;
+ if (ge_frombytes_vartime(&point1, &out_key) != 0) {
+ return false;
+ }
+ derivation_to_scalar(derivation, output_index, scalar);
+ ge_scalarmult_base(&point2, &scalar);
+ ge_p3_to_cached(&point3, &point2);
+ ge_sub(&point4, &point1, &point3);
+ ge_p1p1_to_p2(&point5, &point4);
+ ge_tobytes(&derived_key, &point5);
+ return true;
+ }
+
struct s_comm {
hash h;
ec_point key;
@@ -246,22 +265,33 @@ namespace crypto {
return sc_isnonzero(&c) == 0;
}
- void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const secret_key &r, signature &sig) {
+ void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) {
// sanity check
ge_p3 R_p3;
ge_p3 A_p3;
+ ge_p3 B_p3;
ge_p3 D_p3;
if (ge_frombytes_vartime(&R_p3, &R) != 0) throw std::runtime_error("tx pubkey is invalid");
if (ge_frombytes_vartime(&A_p3, &A) != 0) throw std::runtime_error("recipient view pubkey is invalid");
+ if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) throw std::runtime_error("recipient spend pubkey is invalid");
if (ge_frombytes_vartime(&D_p3, &D) != 0) throw std::runtime_error("key derivation is invalid");
#if !defined(NDEBUG)
{
assert(sc_check(&r) == 0);
- // check R == r*G
- ge_p3 dbg_R_p3;
- ge_scalarmult_base(&dbg_R_p3, &r);
+ // check R == r*G or R == r*B
public_key dbg_R;
- ge_p3_tobytes(&dbg_R, &dbg_R_p3);
+ if (B)
+ {
+ ge_p2 dbg_R_p2;
+ ge_scalarmult(&dbg_R_p2, &r, &B_p3);
+ ge_tobytes(&dbg_R, &dbg_R_p2);
+ }
+ else
+ {
+ ge_p3 dbg_R_p3;
+ ge_scalarmult_base(&dbg_R_p3, &r);
+ ge_p3_tobytes(&dbg_R, &dbg_R_p3);
+ }
assert(R == dbg_R);
// check D == r*A
ge_p2 dbg_D_p2;
@@ -276,43 +306,84 @@ namespace crypto {
ec_scalar k;
random_scalar(k);
- // compute X = k*G
- ge_p3 X_p3;
- ge_scalarmult_base(&X_p3, &k);
+ s_comm_2 buf;
+ buf.msg = prefix_hash;
+ buf.D = D;
+
+ if (B)
+ {
+ // compute X = k*B
+ ge_p2 X_p2;
+ ge_scalarmult(&X_p2, &k, &B_p3);
+ ge_tobytes(&buf.X, &X_p2);
+ }
+ else
+ {
+ // compute X = k*G
+ ge_p3 X_p3;
+ ge_scalarmult_base(&X_p3, &k);
+ ge_p3_tobytes(&buf.X, &X_p3);
+ }
// compute Y = k*A
ge_p2 Y_p2;
ge_scalarmult(&Y_p2, &k, &A_p3);
+ ge_tobytes(&buf.Y, &Y_p2);
// sig.c = Hs(Msg || D || X || Y)
- s_comm_2 buf;
- buf.msg = prefix_hash;
- buf.D = D;
- ge_p3_tobytes(&buf.X, &X_p3);
- ge_tobytes(&buf.Y, &Y_p2);
- hash_to_scalar(&buf, sizeof(s_comm_2), sig.c);
+ hash_to_scalar(&buf, sizeof(buf), sig.c);
// sig.r = k - sig.c*r
sc_mulsub(&sig.r, &sig.c, &r, &k);
}
- bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) {
+ bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig) {
// sanity check
ge_p3 R_p3;
ge_p3 A_p3;
+ ge_p3 B_p3;
ge_p3 D_p3;
if (ge_frombytes_vartime(&R_p3, &R) != 0) return false;
if (ge_frombytes_vartime(&A_p3, &A) != 0) return false;
+ if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) return false;
if (ge_frombytes_vartime(&D_p3, &D) != 0) return false;
if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0) return false;
// compute sig.c*R
- ge_p2 cR_p2;
- ge_scalarmult(&cR_p2, &sig.c, &R_p3);
+ ge_p3 cR_p3;
+ {
+ ge_p2 cR_p2;
+ ge_scalarmult(&cR_p2, &sig.c, &R_p3);
+ public_key cR;
+ ge_tobytes(&cR, &cR_p2);
+ if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false;
+ }
- // compute sig.r*G
- ge_p3 rG_p3;
- ge_scalarmult_base(&rG_p3, &sig.r);
+ ge_p1p1 X_p1p1;
+ if (B)
+ {
+ // compute X = sig.c*R + sig.r*B
+ ge_p2 rB_p2;
+ ge_scalarmult(&rB_p2, &sig.r, &B_p3);
+ public_key rB;
+ ge_tobytes(&rB, &rB_p2);
+ ge_p3 rB_p3;
+ if (ge_frombytes_vartime(&rB_p3, &rB) != 0) return false;
+ ge_cached rB_cached;
+ ge_p3_to_cached(&rB_cached, &rB_p3);
+ ge_add(&X_p1p1, &cR_p3, &rB_cached);
+ }
+ else
+ {
+ // compute X = sig.c*R + sig.r*G
+ ge_p3 rG_p3;
+ ge_scalarmult_base(&rG_p3, &sig.r);
+ ge_cached rG_cached;
+ ge_p3_to_cached(&rG_cached, &rG_p3);
+ ge_add(&X_p1p1, &cR_p3, &rG_cached);
+ }
+ ge_p2 X_p2;
+ ge_p1p1_to_p2(&X_p2, &X_p1p1);
// compute sig.c*D
ge_p2 cD_p2;
@@ -322,18 +393,6 @@ namespace crypto {
ge_p2 rA_p2;
ge_scalarmult(&rA_p2, &sig.r, &A_p3);
- // compute X = sig.c*R + sig.r*G
- public_key cR;
- ge_tobytes(&cR, &cR_p2);
- ge_p3 cR_p3;
- if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false;
- ge_cached rG_cached;
- ge_p3_to_cached(&rG_cached, &rG_p3);
- ge_p1p1 X_p1p1;
- ge_add(&X_p1p1, &cR_p3, &rG_cached);
- ge_p2 X_p2;
- ge_p1p1_to_p2(&X_p2, &X_p1p1);
-
// compute Y = sig.c*D + sig.r*A
public_key cD;
public_key rA;
diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h
index e99b6651f..abdea0165 100644
--- a/src/crypto/crypto.h
+++ b/src/crypto/crypto.h
@@ -31,12 +31,17 @@
#pragma once
#include <cstddef>
+#include <iostream>
#include <boost/thread/mutex.hpp>
#include <boost/thread/lock_guard.hpp>
+#include <boost/utility/value_init.hpp>
+#include <boost/optional.hpp>
#include <vector>
#include "common/pod-class.h"
#include "generic-ops.h"
+#include "hex.h"
+#include "span.h"
#include "hash.h"
namespace crypto {
@@ -94,6 +99,8 @@ namespace crypto {
};
#pragma pack(pop)
+ void hash_to_scalar(const void *data, size_t length, ec_scalar &res);
+
static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 &&
sizeof(public_key) == 32 && sizeof(secret_key) == 32 &&
sizeof(key_derivation) == 32 && sizeof(key_image) == 32 &&
@@ -119,14 +126,16 @@ namespace crypto {
friend bool derive_public_key(const key_derivation &, std::size_t, const public_key &, public_key &);
static void derive_secret_key(const key_derivation &, std::size_t, const secret_key &, secret_key &);
friend void derive_secret_key(const key_derivation &, std::size_t, const secret_key &, secret_key &);
+ static bool derive_subaddress_public_key(const public_key &, const key_derivation &, std::size_t, public_key &);
+ friend bool derive_subaddress_public_key(const public_key &, const key_derivation &, std::size_t, public_key &);
static void generate_signature(const hash &, const public_key &, const secret_key &, signature &);
friend void generate_signature(const hash &, const public_key &, const secret_key &, signature &);
static bool check_signature(const hash &, const public_key &, const signature &);
friend bool check_signature(const hash &, const public_key &, const signature &);
- static void generate_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const secret_key &, signature &);
- friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const secret_key &, signature &);
- static bool check_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const signature &);
- friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const signature &);
+ static void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
+ friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
+ static bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &);
+ friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &);
static void generate_key_image(const public_key &, const secret_key &, key_image &);
friend void generate_key_image(const public_key &, const secret_key &, key_image &);
static void generate_ring_signature(const hash &, const key_image &,
@@ -194,6 +203,9 @@ namespace crypto {
const secret_key &base, secret_key &derived_key) {
crypto_ops::derive_secret_key(derivation, output_index, base, derived_key);
}
+ inline bool derive_subaddress_public_key(const public_key &out_key, const key_derivation &derivation, std::size_t output_index, public_key &result) {
+ return crypto_ops::derive_subaddress_public_key(out_key, derivation, output_index, result);
+ }
/* Generation and checking of a standard signature.
*/
@@ -206,12 +218,13 @@ namespace crypto {
/* Generation and checking of a tx proof; given a tx pubkey R, the recipient's view pubkey A, and the key
* derivation D, the signature proves the knowledge of the tx secret key r such that R=r*G and D=r*A
+ * When the recipient's address is a subaddress, the tx pubkey R is defined as R=r*B where B is the recipient's spend pubkey
*/
- inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const secret_key &r, signature &sig) {
- crypto_ops::generate_tx_proof(prefix_hash, R, A, D, r, sig);
+ inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) {
+ crypto_ops::generate_tx_proof(prefix_hash, R, A, B, D, r, sig);
}
- inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) {
- return crypto_ops::check_tx_proof(prefix_hash, R, A, D, sig);
+ inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig) {
+ return crypto_ops::check_tx_proof(prefix_hash, R, A, B, D, sig);
}
/* To send money to a key:
@@ -248,8 +261,28 @@ namespace crypto {
const signature *sig) {
return check_ring_signature(prefix_hash, image, pubs.data(), pubs.size(), sig);
}
+
+ inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) {
+ epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
+ }
+ inline std::ostream &operator <<(std::ostream &o, const crypto::secret_key &v) {
+ epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
+ }
+ inline std::ostream &operator <<(std::ostream &o, const crypto::key_derivation &v) {
+ epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
+ }
+ inline std::ostream &operator <<(std::ostream &o, const crypto::key_image &v) {
+ epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
+ }
+ inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) {
+ epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
+ }
+
+ const static crypto::public_key null_pkey = boost::value_initialized<crypto::public_key>();
+ const static crypto::secret_key null_skey = boost::value_initialized<crypto::secret_key>();
}
CRYPTO_MAKE_HASHABLE(public_key)
+CRYPTO_MAKE_HASHABLE(secret_key)
CRYPTO_MAKE_HASHABLE(key_image)
CRYPTO_MAKE_COMPARABLE(signature)
diff --git a/src/crypto/hash.h b/src/crypto/hash.h
index 22991e513..610b4502f 100644
--- a/src/crypto/hash.h
+++ b/src/crypto/hash.h
@@ -31,9 +31,13 @@
#pragma once
#include <stddef.h>
+#include <iostream>
+#include <boost/utility/value_init.hpp>
#include "common/pod-class.h"
#include "generic-ops.h"
+#include "hex.h"
+#include "span.h"
namespace crypto {
@@ -75,6 +79,15 @@ namespace crypto {
tree_hash(reinterpret_cast<const char (*)[HASH_SIZE]>(hashes), count, reinterpret_cast<char *>(&root_hash));
}
+ inline std::ostream &operator <<(std::ostream &o, const crypto::hash &v) {
+ epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
+ }
+ inline std::ostream &operator <<(std::ostream &o, const crypto::hash8 &v) {
+ epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
+ }
+
+ const static crypto::hash null_hash = boost::value_initialized<crypto::hash>();
+ const static crypto::hash8 null_hash8 = boost::value_initialized<crypto::hash8>();
}
CRYPTO_MAKE_HASHABLE(hash)
diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c
index b92b6e6c3..cc234713b 100644
--- a/src/crypto/slow-hash.c
+++ b/src/crypto/slow-hash.c
@@ -47,7 +47,7 @@
extern int aesb_single_round(const uint8_t *in, uint8_t*out, const uint8_t *expandedKey);
extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey);
-#if defined(__x86_64__) || (defined(_MSC_VER) && defined(_WIN64))
+#if !defined NO_AES && (defined(__x86_64__) || (defined(_MSC_VER) && defined(_WIN64)))
// Optimised code below, uses x86-specific intrinsics, SSE2, AES-NI
// Fall back to more portable code is down at the bottom
@@ -645,7 +645,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash)
extra_hashes[state.hs.b[0] & 3](&state, 200, hash);
}
-#elif defined(__arm__) || defined(__aarch64__)
+#elif !defined NO_AES && (defined(__arm__) || defined(__aarch64__))
void slow_hash_allocate_state(void)
{
// Do nothing, this is just to maintain compatibility with the upgraded slow-hash.c
diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt
index 1503b277e..750be69f1 100644
--- a/src/cryptonote_basic/CMakeLists.txt
+++ b/src/cryptonote_basic/CMakeLists.txt
@@ -34,7 +34,6 @@ endif()
set(cryptonote_basic_sources
account.cpp
- checkpoints.cpp
cryptonote_basic_impl.cpp
cryptonote_format_utils.cpp
difficulty.cpp
@@ -46,7 +45,6 @@ set(cryptonote_basic_headers)
set(cryptonote_basic_private_headers
account.h
account_boost_serialization.h
- checkpoints.h
connection_context.h
cryptonote_basic.h
cryptonote_basic_impl.h
@@ -60,7 +58,7 @@ set(cryptonote_basic_private_headers
verification_context.h)
monero_private_headers(cryptonote_basic
- ${crypto_private_headers})
+ ${cryptonote_basic_private_headers})
monero_add_library(cryptonote_basic
${cryptonote_basic_sources}
${cryptonote_basic_headers}
@@ -69,6 +67,7 @@ target_link_libraries(cryptonote_basic
PUBLIC
common
cncrypto
+ checkpoints
${Boost_DATE_TIME_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_SERIALIZATION_LIBRARY}
diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp
index dd875402f..fb832d88e 100644
--- a/src/cryptonote_basic/account.cpp
+++ b/src/cryptonote_basic/account.cpp
@@ -131,7 +131,7 @@ DISABLE_VS_WARNINGS(4244 4345)
std::string account_base::get_public_address_str(bool testnet) const
{
//TODO: change this code into base 58
- return get_account_address_as_str(testnet, m_keys.m_account_address);
+ return get_account_address_as_str(testnet, false, m_keys.m_account_address);
}
//-----------------------------------------------------------------
std::string account_base::get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const
diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h
index 3283543e2..e173348db 100644
--- a/src/cryptonote_basic/connection_context.h
+++ b/src/cryptonote_basic/connection_context.h
@@ -40,7 +40,7 @@ namespace cryptonote
struct cryptonote_connection_context: public epee::net_utils::connection_context_base
{
cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0),
- m_last_known_hash(cryptonote::null_hash) {}
+ m_callback_request_count(0), m_last_known_hash(crypto::null_hash) {}
enum state
{
diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h
index c4adf1fcb..89dda8c3d 100644
--- a/src/cryptonote_basic/cryptonote_basic.h
+++ b/src/cryptonote_basic/cryptonote_basic.h
@@ -53,11 +53,6 @@
namespace cryptonote
{
-
- const static crypto::hash null_hash = AUTO_VAL_INIT(null_hash);
- const static crypto::hash8 null_hash8 = AUTO_VAL_INIT(null_hash8);
- const static crypto::public_key null_pkey = AUTO_VAL_INIT(null_pkey);
-
typedef std::vector<crypto::signature> ring_signature;
@@ -416,6 +411,17 @@ namespace cryptonote
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_public_key)
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_public_key)
END_KV_SERIALIZE_MAP()
+
+ bool operator==(const account_public_address& rhs) const
+ {
+ return m_spend_public_key == rhs.m_spend_public_key &&
+ m_view_public_key == rhs.m_view_public_key;
+ }
+
+ bool operator!=(const account_public_address& rhs) const
+ {
+ return !(*this == rhs);
+ }
};
struct keypair
@@ -434,6 +440,21 @@ namespace cryptonote
}
+namespace std {
+ template <>
+ struct hash<cryptonote::account_public_address>
+ {
+ std::size_t operator()(const cryptonote::account_public_address& addr) const
+ {
+ // https://stackoverflow.com/a/17017281
+ size_t res = 17;
+ res = res * 31 + hash<crypto::public_key>()(addr.m_spend_public_key);
+ res = res * 31 + hash<crypto::public_key>()(addr.m_view_public_key);
+ return res;
+ }
+ };
+}
+
BLOB_SERIALIZER(cryptonote::txout_to_key);
BLOB_SERIALIZER(cryptonote::txout_to_scripthash);
diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp
index a59f96956..1183fda06 100644
--- a/src/cryptonote_basic/cryptonote_basic_impl.cpp
+++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp
@@ -158,11 +158,13 @@ namespace cryptonote {
//-----------------------------------------------------------------------
std::string get_account_address_as_str(
bool testnet
+ , bool subaddress
, account_public_address const & adr
)
{
uint64_t address_prefix = testnet ?
- config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX;
+ (subaddress ? config::testnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX) :
+ (subaddress ? config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX);
return tools::base58::encode_addr(address_prefix, t_serializable_object_to_blob(adr));
}
@@ -173,8 +175,7 @@ namespace cryptonote {
, crypto::hash8 const & payment_id
)
{
- uint64_t integrated_address_prefix = testnet ?
- config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
+ uint64_t integrated_address_prefix = testnet ? config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
integrated_address iadr = {
adr, payment_id
@@ -193,10 +194,8 @@ namespace cryptonote {
return true;
}
//-----------------------------------------------------------------------
- bool get_account_integrated_address_from_str(
- account_public_address& adr
- , bool& has_payment_id
- , crypto::hash8& payment_id
+ bool get_account_address_from_str(
+ address_parse_info& info
, bool testnet
, std::string const & str
)
@@ -205,6 +204,8 @@ namespace cryptonote {
config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX;
uint64_t integrated_address_prefix = testnet ?
config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
+ uint64_t subaddress_prefix = testnet ?
+ config::testnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX;
if (2 * sizeof(public_address_outer_blob) != str.size())
{
@@ -218,18 +219,27 @@ namespace cryptonote {
if (integrated_address_prefix == prefix)
{
- has_payment_id = true;
+ info.is_subaddress = false;
+ info.has_payment_id = true;
}
else if (address_prefix == prefix)
{
- has_payment_id = false;
+ info.is_subaddress = false;
+ info.has_payment_id = false;
+ }
+ else if (subaddress_prefix == prefix)
+ {
+ info.is_subaddress = true;
+ info.has_payment_id = false;
}
else {
- LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << address_prefix << " or " << integrated_address_prefix);
+ LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << address_prefix
+ << " or " << integrated_address_prefix
+ << " or " << subaddress_prefix);
return false;
}
- if (has_payment_id)
+ if (info.has_payment_id)
{
integrated_address iadr;
if (!::serialization::parse_binary(data, iadr))
@@ -237,19 +247,19 @@ namespace cryptonote {
LOG_PRINT_L1("Account public address keys can't be parsed");
return false;
}
- adr = iadr.adr;
- payment_id = iadr.payment_id;
+ info.address = iadr.adr;
+ info.payment_id = iadr.payment_id;
}
else
{
- if (!::serialization::parse_binary(data, adr))
+ if (!::serialization::parse_binary(data, info.address))
{
LOG_PRINT_L1("Account public address keys can't be parsed");
return false;
}
}
- if (!crypto::check_key(adr.m_spend_public_key) || !crypto::check_key(adr.m_view_public_key))
+ if (!crypto::check_key(info.address.m_spend_public_key) || !crypto::check_key(info.address.m_view_public_key))
{
LOG_PRINT_L1("Failed to validate address keys");
return false;
@@ -284,51 +294,27 @@ namespace cryptonote {
}
//we success
- adr = blob.m_address;
- has_payment_id = false;
+ info.address = blob.m_address;
+ info.is_subaddress = false;
+ info.has_payment_id = false;
}
return true;
}
- //-----------------------------------------------------------------------
- bool get_account_address_from_str(
- account_public_address& adr
- , bool testnet
- , std::string const & str
- )
- {
- bool has_payment_id;
- crypto::hash8 payment_id;
- return get_account_integrated_address_from_str(adr, has_payment_id, payment_id, testnet, str);
- }
//--------------------------------------------------------------------------------
bool get_account_address_from_str_or_url(
- cryptonote::account_public_address& address
- , bool& has_payment_id
- , crypto::hash8& payment_id
+ address_parse_info& info
, bool testnet
, const std::string& str_or_url
, std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm
)
{
- if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, str_or_url))
+ if (get_account_address_from_str(info, testnet, str_or_url))
return true;
bool dnssec_valid;
std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(str_or_url, dnssec_valid, dns_confirm);
return !address_str.empty() &&
- get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_str);
- }
- //--------------------------------------------------------------------------------
- bool get_account_address_from_str_or_url(
- cryptonote::account_public_address& address
- , bool testnet
- , const std::string& str_or_url
- , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm
- )
- {
- bool has_payment_id;
- crypto::hash8 payment_id;
- return get_account_address_from_str_or_url(address, has_payment_id, payment_id, testnet, str_or_url, dns_confirm);
+ get_account_address_from_str(info, testnet, address_str);
}
//--------------------------------------------------------------------------------
bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b) {
diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h
index 7a2259b32..08d966fed 100644
--- a/src/cryptonote_basic/cryptonote_basic_impl.h
+++ b/src/cryptonote_basic/cryptonote_basic_impl.h
@@ -33,8 +33,6 @@
#include "cryptonote_basic.h"
#include "crypto/crypto.h"
#include "crypto/hash.h"
-#include "hex.h"
-#include "span.h"
namespace cryptonote {
@@ -77,6 +75,14 @@ namespace cryptonote {
}
}
+ struct address_parse_info
+ {
+ account_public_address address;
+ bool is_subaddress;
+ bool has_payment_id;
+ crypto::hash8 payment_id;
+ };
+
/************************************************************************/
/* Cryptonote helper functions */
/************************************************************************/
@@ -89,6 +95,7 @@ namespace cryptonote {
std::string get_account_address_as_str(
bool testnet
+ , bool subaddress
, const account_public_address& adr
);
@@ -98,31 +105,14 @@ namespace cryptonote {
, const crypto::hash8& payment_id
);
- bool get_account_integrated_address_from_str(
- account_public_address& adr
- , bool& has_payment_id
- , crypto::hash8& payment_id
- , bool testnet
- , const std::string& str
- );
-
bool get_account_address_from_str(
- account_public_address& adr
+ address_parse_info& info
, bool testnet
, const std::string& str
);
bool get_account_address_from_str_or_url(
- cryptonote::account_public_address& address
- , bool& has_payment_id
- , crypto::hash8& payment_id
- , bool testnet
- , const std::string& str_or_url
- , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm = return_first_address
- );
-
- bool get_account_address_from_str_or_url(
- cryptonote::account_public_address& address
+ address_parse_info& info
, bool testnet
, const std::string& str_or_url
, std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm = return_first_address
@@ -136,26 +126,3 @@ namespace cryptonote {
bool parse_hash256(const std::string str_hash, crypto::hash& hash);
-namespace crypto {
- inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) {
- epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
- }
- inline std::ostream &operator <<(std::ostream &o, const crypto::secret_key &v) {
- epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
- }
- inline std::ostream &operator <<(std::ostream &o, const crypto::key_derivation &v) {
- epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
- }
- inline std::ostream &operator <<(std::ostream &o, const crypto::key_image &v) {
- epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
- }
- inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) {
- epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
- }
- inline std::ostream &operator <<(std::ostream &o, const crypto::hash &v) {
- epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
- }
- inline std::ostream &operator <<(std::ostream &o, const crypto::hash8 &v) {
- epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
- }
-}
diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h
index 6e4ac9b72..a67fa0ae7 100644
--- a/src/cryptonote_basic/cryptonote_boost_serialization.h
+++ b/src/cryptonote_basic/cryptonote_boost_serialization.h
@@ -83,6 +83,11 @@ namespace boost
{
a & reinterpret_cast<char (&)[sizeof(crypto::hash)]>(x);
}
+ template <class Archive>
+ inline void serialize(Archive &a, crypto::hash8 &x, const boost::serialization::version_type ver)
+ {
+ a & reinterpret_cast<char (&)[sizeof(crypto::hash8)]>(x);
+ }
template <class Archive>
inline void serialize(Archive &a, cryptonote::txout_to_script &x, const boost::serialization::version_type ver)
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index 745dfb72e..d09f4c432 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -129,16 +129,69 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
- bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki)
+ crypto::secret_key get_subaddress_secret_key(const crypto::secret_key& a, const subaddress_index& index)
+ {
+ const char prefix[] = "SubAddr";
+ char data[sizeof(prefix) + sizeof(crypto::secret_key) + sizeof(subaddress_index)];
+ memcpy(data, prefix, sizeof(prefix));
+ memcpy(data + sizeof(prefix), &a, sizeof(crypto::secret_key));
+ memcpy(data + sizeof(prefix) + sizeof(crypto::secret_key), &index, sizeof(subaddress_index));
+ crypto::secret_key m;
+ crypto::hash_to_scalar(data, sizeof(data), m);
+ return m;
+ }
+ //---------------------------------------------------------------
+ bool generate_key_image_helper(const account_keys& ack, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key& tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki)
{
crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation);
bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation);
CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")");
- r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub);
- CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")");
+ std::vector<crypto::key_derivation> additional_recv_derivations;
+ for (size_t i = 0; i < additional_tx_public_keys.size(); ++i)
+ {
+ crypto::key_derivation additional_recv_derivation = AUTO_VAL_INIT(additional_recv_derivation);
+ r = crypto::generate_key_derivation(additional_tx_public_keys[i], ack.m_view_secret_key, additional_recv_derivation);
+ CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << additional_tx_public_keys[i] << ", " << ack.m_view_secret_key << ")");
+ additional_recv_derivations.push_back(additional_recv_derivation);
+ }
+
+ boost::optional<subaddress_receive_info> subaddr_recv_info = is_out_to_acc_precomp(subaddresses, out_key, recv_derivation, additional_recv_derivations, real_output_index);
+ CHECK_AND_ASSERT_MES(subaddr_recv_info, false, "key image helper: given output pubkey doesn't seem to belong to this address");
- crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec);
+ return generate_key_image_helper_precomp(ack, out_key, subaddr_recv_info->derivation, real_output_index, subaddr_recv_info->index, in_ephemeral, ki);
+ }
+ //---------------------------------------------------------------
+ bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki)
+ {
+ if (ack.m_spend_secret_key == crypto::null_skey)
+ {
+ // for watch-only wallet, simply copy the known output pubkey
+ in_ephemeral.pub = out_key;
+ in_ephemeral.sec = crypto::null_skey;
+ }
+ else
+ {
+ // derive secret key with subaddress - step 1: original CN derivation
+ crypto::secret_key scalar_step1;
+ crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b
+
+ // step 2: add Hs(a || index_major || index_minor)
+ crypto::secret_key scalar_step2;
+ if (received_index.is_zero())
+ {
+ scalar_step2 = scalar_step1; // treat index=(0,0) as a special case representing the main address
+ }
+ else
+ {
+ crypto::secret_key m = get_subaddress_secret_key(ack.m_view_secret_key, received_index);
+ sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&m);
+ }
+
+ in_ephemeral.sec = scalar_step2;
+ crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub);
+ CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, false, "key image helper precomp: given output pubkey doesn't match the derived one");
+ }
crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki);
return true;
@@ -271,9 +324,53 @@ namespace cryptonote
//---------------------------------------------------------------
bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key)
{
- tx.extra.resize(tx.extra.size() + 1 + sizeof(crypto::public_key));
- tx.extra[tx.extra.size() - 1 - sizeof(crypto::public_key)] = TX_EXTRA_TAG_PUBKEY;
- *reinterpret_cast<crypto::public_key*>(&tx.extra[tx.extra.size() - sizeof(crypto::public_key)]) = tx_pub_key;
+ return add_tx_pub_key_to_extra(tx.extra, tx_pub_key);
+ }
+ //---------------------------------------------------------------
+ bool add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key)
+ {
+ return add_tx_pub_key_to_extra(tx.extra, tx_pub_key);
+ }
+ //---------------------------------------------------------------
+ bool add_tx_pub_key_to_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& tx_pub_key)
+ {
+ tx_extra.resize(tx_extra.size() + 1 + sizeof(crypto::public_key));
+ tx_extra[tx_extra.size() - 1 - sizeof(crypto::public_key)] = TX_EXTRA_TAG_PUBKEY;
+ *reinterpret_cast<crypto::public_key*>(&tx_extra[tx_extra.size() - sizeof(crypto::public_key)]) = tx_pub_key;
+ return true;
+ }
+ //---------------------------------------------------------------
+ std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const std::vector<uint8_t>& tx_extra)
+ {
+ // parse
+ std::vector<tx_extra_field> tx_extra_fields;
+ parse_tx_extra(tx_extra, tx_extra_fields);
+ // find corresponding field
+ tx_extra_additional_pub_keys additional_pub_keys;
+ if(!find_tx_extra_field_by_type(tx_extra_fields, additional_pub_keys))
+ return {};
+ return additional_pub_keys.data;
+ }
+ //---------------------------------------------------------------
+ std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx)
+ {
+ return get_additional_tx_pub_keys_from_extra(tx.extra);
+ }
+ //---------------------------------------------------------------
+ bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys)
+ {
+ // convert to variant
+ tx_extra_field field = tx_extra_additional_pub_keys{ additional_pub_keys };
+ // serialize
+ std::ostringstream oss;
+ binary_archive<true> ar(oss);
+ bool r = ::do_serialize(ar, field);
+ CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra additional tx pub keys");
+ // append
+ std::string tx_extra_str = oss.str();
+ size_t pos = tx_extra.size();
+ tx_extra.resize(tx_extra.size() + tx_extra_str.size());
+ memcpy(&tx_extra[pos], tx_extra_str.data(), tx_extra_str.size());
return true;
}
//---------------------------------------------------------------
@@ -480,20 +577,43 @@ namespace cryptonote
return res;
}
//---------------------------------------------------------------
- bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index)
+ bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, size_t output_index)
{
crypto::key_derivation derivation;
generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation);
crypto::public_key pk;
derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk);
- return pk == out_key.key;
+ if (pk == out_key.key)
+ return true;
+ // try additional tx pubkeys if available
+ if (!additional_tx_pub_keys.empty())
+ {
+ CHECK_AND_ASSERT_MES(output_index < additional_tx_pub_keys.size(), false, "wrong number of additional tx pubkeys");
+ generate_key_derivation(additional_tx_pub_keys[output_index], acc.m_view_secret_key, derivation);
+ derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk);
+ return pk == out_key.key;
+ }
+ return false;
}
//---------------------------------------------------------------
- bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index)
+ boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index)
{
- crypto::public_key pk;
- derive_public_key(derivation, output_index, spend_public_key, pk);
- return pk == out_key.key;
+ // try the shared tx pubkey
+ crypto::public_key subaddress_spendkey;
+ derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey);
+ auto found = subaddresses.find(subaddress_spendkey);
+ if (found != subaddresses.end())
+ return subaddress_receive_info{ found->second, derivation };
+ // try additional tx pubkeys if available
+ if (!additional_derivations.empty())
+ {
+ CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations");
+ derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey);
+ found = subaddresses.find(subaddress_spendkey);
+ if (found != subaddresses.end())
+ return subaddress_receive_info{ found->second, additional_derivations[output_index] };
+ }
+ return boost::none;
}
//---------------------------------------------------------------
bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered)
@@ -501,17 +621,19 @@ namespace cryptonote
crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
if(null_pkey == tx_pub_key)
return false;
- return lookup_acc_outs(acc, tx, tx_pub_key, outs, money_transfered);
+ std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
+ return lookup_acc_outs(acc, tx, tx_pub_key, additional_tx_pub_keys, outs, money_transfered);
}
//---------------------------------------------------------------
- bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered)
+ bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, std::vector<size_t>& outs, uint64_t& money_transfered)
{
+ CHECK_AND_ASSERT_MES(additional_tx_pub_keys.empty() || additional_tx_pub_keys.size() == tx.vout.size(), false, "wrong number of additional pubkeys" );
money_transfered = 0;
size_t i = 0;
for(const tx_out& o: tx.vout)
{
CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong type id in transaction out" );
- if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i))
+ if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, additional_tx_pub_keys, i))
{
outs.push_back(i);
money_transfered += o.amount;
@@ -632,7 +754,7 @@ namespace cryptonote
// prunable rct
if (t.rct_signatures.type == rct::RCTTypeNull)
{
- hashes[2] = cryptonote::null_hash;
+ hashes[2] = crypto::null_hash;
}
else
{
@@ -869,4 +991,21 @@ namespace cryptonote
block_hashes_calculated = block_hashes_calculated_count;
block_hashes_cached = block_hashes_cached_count;
}
+ //---------------------------------------------------------------
+ crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase)
+ {
+ crypto::hash hash;
+ crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash);
+ sc_add((unsigned char*)key.data, (const unsigned char*)key.data, (const unsigned char*)hash.data);
+ return key;
+ }
+ //---------------------------------------------------------------
+ crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase)
+ {
+ crypto::hash hash;
+ crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash);
+ sc_sub((unsigned char*)key.data, (const unsigned char*)key.data, (const unsigned char*)hash.data);
+ return key;
+ }
+
}
diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h
index d8ccf8eec..f88310c4c 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.h
+++ b/src/cryptonote_basic/cryptonote_format_utils.h
@@ -32,9 +32,11 @@
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
#include "cryptonote_basic_impl.h"
#include "account.h"
+#include "subaddress_index.h"
#include "include_base_utils.h"
#include "crypto/crypto.h"
#include "crypto/hash.h"
+#include <unordered_map>
namespace cryptonote
{
@@ -63,19 +65,31 @@ namespace cryptonote
crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0);
crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index = 0);
bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key);
+ bool add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key);
+ bool add_tx_pub_key_to_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& tx_pub_key);
+ std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const std::vector<uint8_t>& tx_extra);
+ std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx);
+ bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys);
bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce);
bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type);
void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id);
void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id);
bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id);
bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id);
- bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index);
- bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index);
- bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered);
+ bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index);
+ struct subaddress_receive_info
+ {
+ subaddress_index index;
+ crypto::key_derivation derivation;
+ };
+ boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index);
+ bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, std::vector<size_t>& outs, uint64_t& money_transfered);
bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered);
bool get_tx_fee(const transaction& tx, uint64_t & fee);
uint64_t get_tx_fee(const transaction& tx);
- bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki);
+ crypto::secret_key get_subaddress_secret_key(const crypto::secret_key& a, const subaddress_index& index);
+ bool generate_key_image_helper(const account_keys& ack, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key& tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki);
+ bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki);
void get_blob_hash(const blobdata& blob, crypto::hash& res);
crypto::hash get_blob_hash(const blobdata& blob);
std::string short_hash_str(const crypto::hash& h);
@@ -212,6 +226,8 @@ namespace cryptonote
bool is_valid_decomposed_amount(uint64_t amount);
void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached);
+ crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase);
+ crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase);
#define CHECKED_GET_SPECIFIC_VARIANT(variant_var, specific_type, variable_name, fail_return_val) \
CHECK_AND_ASSERT_MES(variant_var.type() == typeid(specific_type), fail_return_val, "wrong variant type: " << variant_var.type().name() << ", expected " << typeid(specific_type).name()); \
specific_type& variable_name = boost::get<specific_type>(variant_var);
diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp
index 3c5811d61..0aafe24e1 100644
--- a/src/cryptonote_basic/miner.cpp
+++ b/src/cryptonote_basic/miner.cpp
@@ -228,11 +228,13 @@ namespace cryptonote
if(command_line::has_arg(vm, arg_start_mining))
{
- if(!cryptonote::get_account_address_from_str(m_mine_address, testnet, command_line::get_arg(vm, arg_start_mining)))
+ address_parse_info info;
+ if(!cryptonote::get_account_address_from_str(info, testnet, command_line::get_arg(vm, arg_start_mining)) || info.is_subaddress)
{
LOG_ERROR("Target account address " << command_line::get_arg(vm, arg_start_mining) << " has wrong format, starting daemon canceled");
return false;
}
+ m_mine_address = info.address;
m_threads_total = 1;
m_do_mining = true;
if(command_line::has_arg(vm, arg_mining_threads))
@@ -289,8 +291,7 @@ namespace cryptonote
return false;
}
- if(!m_template_no)
- request_block_template();//lets update block template
+ request_block_template();//lets update block template
boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0);
boost::interprocess::ipcdetail::atomic_write32(&m_thread_index, 0);
@@ -858,19 +859,6 @@ namespace cryptonote
const boost::filesystem::path& power_supply_path = iter->path();
if (boost::filesystem::is_directory(power_supply_path))
{
- std::ifstream power_supply_present_stream((power_supply_path / "present").string());
- if (power_supply_present_stream.fail())
- {
- LOG_PRINT_L0("Unable to read from " << power_supply_path << " to check if power supply present");
- continue;
- }
-
- if (power_supply_present_stream.get() != '1')
- {
- LOG_PRINT_L4("Power supply not present at " << power_supply_path);
- continue;
- }
-
boost::filesystem::path power_supply_type_path = power_supply_path / "type";
if (boost::filesystem::is_regular_file(power_supply_type_path))
{
diff --git a/src/cryptonote_basic/subaddress_index.h b/src/cryptonote_basic/subaddress_index.h
new file mode 100644
index 000000000..07d13c503
--- /dev/null
+++ b/src/cryptonote_basic/subaddress_index.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2017, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "serialization/keyvalue_serialization.h"
+#include <boost/serialization/serialization.hpp>
+#include <boost/serialization/version.hpp>
+#include <ostream>
+
+namespace cryptonote
+{
+ struct subaddress_index
+ {
+ uint32_t major;
+ uint32_t minor;
+ bool operator==(const subaddress_index& rhs) const { return !memcmp(this, &rhs, sizeof(subaddress_index)); }
+ bool operator!=(const subaddress_index& rhs) const { return !(*this == rhs); }
+ bool is_zero() const { return major == 0 && minor == 0; }
+
+ BEGIN_SERIALIZE_OBJECT()
+ FIELD(major)
+ FIELD(minor)
+ END_SERIALIZE()
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(major)
+ KV_SERIALIZE(minor)
+ END_KV_SERIALIZE_MAP()
+ };
+}
+
+namespace cryptonote {
+ inline std::ostream& operator<<(std::ostream& out, const cryptonote::subaddress_index& subaddr_index)
+ {
+ return out << subaddr_index.major << '/' << subaddr_index.minor;
+ }
+}
+
+namespace std
+{
+ template <>
+ struct hash<cryptonote::subaddress_index>
+ {
+ size_t operator()(const cryptonote::subaddress_index& index ) const
+ {
+ size_t res;
+ if (sizeof(size_t) == 8)
+ {
+ res = ((uint64_t)index.major << 32) | index.minor;
+ }
+ else
+ {
+ // https://stackoverflow.com/a/17017281
+ res = 17;
+ res = res * 31 + hash<uint32_t>()(index.major);
+ res = res * 31 + hash<uint32_t>()(index.minor);
+ }
+ return res;
+ }
+ };
+}
+
+BOOST_CLASS_VERSION(cryptonote::subaddress_index, 0)
+
+namespace boost
+{
+ namespace serialization
+ {
+ template <class Archive>
+ inline void serialize(Archive &a, cryptonote::subaddress_index &x, const boost::serialization::version_type ver)
+ {
+ a & x.major;
+ a & x.minor;
+ }
+ }
+}
diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h
index 5a6c3176d..e12828a9f 100644
--- a/src/cryptonote_basic/tx_extra.h
+++ b/src/cryptonote_basic/tx_extra.h
@@ -38,6 +38,7 @@
#define TX_EXTRA_TAG_PUBKEY 0x01
#define TX_EXTRA_NONCE 0x02
#define TX_EXTRA_MERGE_MINING_TAG 0x03
+#define TX_EXTRA_TAG_ADDITIONAL_PUBKEYS 0x04
#define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE
#define TX_EXTRA_NONCE_PAYMENT_ID 0x00
@@ -159,6 +160,16 @@ namespace cryptonote
}
};
+ // per-output additional tx pubkey for multi-destination transfers involving at least one subaddress
+ struct tx_extra_additional_pub_keys
+ {
+ std::vector<crypto::public_key> data;
+
+ BEGIN_SERIALIZE()
+ FIELD(data)
+ END_SERIALIZE()
+ };
+
struct tx_extra_mysterious_minergate
{
std::string data;
@@ -172,11 +183,12 @@ namespace cryptonote
// varint tag;
// varint size;
// varint data[];
- typedef boost::variant<tx_extra_padding, tx_extra_pub_key, tx_extra_nonce, tx_extra_merge_mining_tag, tx_extra_mysterious_minergate> tx_extra_field;
+ typedef boost::variant<tx_extra_padding, tx_extra_pub_key, tx_extra_nonce, tx_extra_merge_mining_tag, tx_extra_additional_pub_keys, tx_extra_mysterious_minergate> tx_extra_field;
}
VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_pub_key, TX_EXTRA_TAG_PUBKEY);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_nonce, TX_EXTRA_NONCE);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_merge_mining_tag, TX_EXTRA_MERGE_MINING_TAG);
+VARIANT_TAG(binary_archive, cryptonote::tx_extra_additional_pub_keys, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_mysterious_minergate, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG);
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index cdb46dda9..e38e15cb2 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -91,7 +91,6 @@
#define BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT 10000 //by default, blocks ids count in synchronizing
#define BLOCKS_SYNCHRONIZING_DEFAULT_COUNT_PRE_V4 100 //by default, blocks count in blocks downloading
#define BLOCKS_SYNCHRONIZING_DEFAULT_COUNT 20 //by default, blocks count in blocks downloading
-#define CRYPTONOTE_PROTOCOL_HOP_RELAX_COUNT 3 //value of hop, after which we use only announce of new block
#define CRYPTONOTE_MEMPOOL_TX_LIVETIME 86400 //seconds, one day
#define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME 604800 //seconds, one week
@@ -137,6 +136,9 @@
#define PER_KB_FEE_QUANTIZATION_DECIMALS 8
+#define HASH_OF_HASHES_STEP 256
+
+
// New constants are intended to go here
namespace config
{
@@ -148,6 +150,7 @@ namespace config
uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 18;
uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19;
+ uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 42;
uint16_t const P2P_DEFAULT_PORT = 18080;
uint16_t const RPC_DEFAULT_PORT = 18081;
uint16_t const ZMQ_RPC_DEFAULT_PORT = 18082;
@@ -161,6 +164,7 @@ namespace config
{
uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53;
uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 54;
+ uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 63;
uint16_t const P2P_DEFAULT_PORT = 28080;
uint16_t const RPC_DEFAULT_PORT = 28081;
uint16_t const ZMQ_RPC_DEFAULT_PORT = 28082;
diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt
index 7c43323d4..169a38f0a 100644
--- a/src/cryptonote_core/CMakeLists.txt
+++ b/src/cryptonote_core/CMakeLists.txt
@@ -48,13 +48,14 @@ else()
endif()
monero_private_headers(cryptonote_core
- ${crypto_private_headers})
+ ${cryptonote_core_private_headers})
monero_add_library(cryptonote_core
${cryptonote_core_sources}
${cryptonote_core_headers}
${cryptonote_core_private_headers})
target_link_libraries(cryptonote_core
PUBLIC
+ version
common
cncrypto
blockchain_db
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 619dbdc07..9d89c6280 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -49,7 +49,6 @@
#include "common/boost_serialization_helper.h"
#include "warnings.h"
#include "crypto/hash.h"
-#include "cryptonote_basic/checkpoints.h"
#include "cryptonote_core.h"
#include "ringct/rctSigs.h"
#include "common/perf_timer.h"
@@ -140,14 +139,20 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool) :
bool Blockchain::have_tx(const crypto::hash &id) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
- CRITICAL_REGION_LOCAL(m_blockchain_lock);
+ // WARNING: this function does not take m_blockchain_lock, and thus should only call read only
+ // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as
+ // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must
+ // lock if it is otherwise needed.
return m_db->tx_exists(id);
}
//------------------------------------------------------------------
bool Blockchain::have_tx_keyimg_as_spent(const crypto::key_image &key_im) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
- CRITICAL_REGION_LOCAL(m_blockchain_lock);
+ // WARNING: this function does not take m_blockchain_lock, and thus should only call read only
+ // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as
+ // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must
+ // lock if it is otherwise needed.
return m_db->has_key_image(key_im);
}
//------------------------------------------------------------------
@@ -189,7 +194,12 @@ bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_ke
{
try
{
- m_db->get_output_key(tx_in_to_key.amount, absolute_offsets, outputs);
+ m_db->get_output_key(tx_in_to_key.amount, absolute_offsets, outputs, true);
+ if (absolute_offsets.size() != outputs.size())
+ {
+ MERROR_VER("Output does not exist! amount = " << tx_in_to_key.amount);
+ return false;
+ }
}
catch (...)
{
@@ -209,7 +219,12 @@ bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_ke
add_offsets.push_back(absolute_offsets[i]);
try
{
- m_db->get_output_key(tx_in_to_key.amount, add_offsets, add_outputs);
+ m_db->get_output_key(tx_in_to_key.amount, add_offsets, add_outputs, true);
+ if (add_offsets.size() != add_outputs.size())
+ {
+ MERROR_VER("Output does not exist! amount = " << tx_in_to_key.amount);
+ return false;
+ }
}
catch (...)
{
@@ -278,7 +293,10 @@ bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_ke
uint64_t Blockchain::get_current_blockchain_height() const
{
LOG_PRINT_L3("Blockchain::" << __func__);
- CRITICAL_REGION_LOCAL(m_blockchain_lock);
+ // WARNING: this function does not take m_blockchain_lock, and thus should only call read only
+ // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as
+ // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must
+ // lock if it is otherwise needed.
return m_db->height();
}
//------------------------------------------------------------------
@@ -562,7 +580,10 @@ crypto::hash Blockchain::get_tail_id(uint64_t& height) const
crypto::hash Blockchain::get_tail_id() const
{
LOG_PRINT_L3("Blockchain::" << __func__);
- CRITICAL_REGION_LOCAL(m_blockchain_lock);
+ // WARNING: this function does not take m_blockchain_lock, and thus should only call read only
+ // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as
+ // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must
+ // lock if it is otherwise needed.
return m_db->top_block_hash();
}
//------------------------------------------------------------------
@@ -624,7 +645,10 @@ bool Blockchain::get_short_chain_history(std::list<crypto::hash>& ids) const
crypto::hash Blockchain::get_block_id_by_height(uint64_t height) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
- CRITICAL_REGION_LOCAL(m_blockchain_lock);
+ // WARNING: this function does not take m_blockchain_lock, and thus should only call read only
+ // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as
+ // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must
+ // lock if it is otherwise needed.
try
{
return m_db->get_block_hash_from_height(height);
@@ -1445,7 +1469,7 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list<std::
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
- if(start_offset > m_db->height())
+ if(start_offset >= m_db->height())
return false;
if (!get_blocks(start_offset, count, blocks))
@@ -1467,7 +1491,7 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list<std::
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
- if(start_offset > m_db->height())
+ if(start_offset >= m_db->height())
return false;
for(size_t i = start_offset; i < start_offset + count && i < m_db->height();i++)
@@ -1919,7 +1943,10 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc
uint64_t Blockchain::block_difficulty(uint64_t i) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
- CRITICAL_REGION_LOCAL(m_blockchain_lock);
+ // WARNING: this function does not take m_blockchain_lock, and thus should only call read only
+ // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as
+ // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must
+ // lock if it is otherwise needed.
try
{
return m_db->get_block_difficulty(i);
@@ -2200,7 +2227,10 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, block_verification_
size_t Blockchain::get_total_transactions() const
{
LOG_PRINT_L3("Blockchain::" << __func__);
- CRITICAL_REGION_LOCAL(m_blockchain_lock);
+ // WARNING: this function does not take m_blockchain_lock, and thus should only call read only
+ // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as
+ // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must
+ // lock if it is otherwise needed.
return m_db->get_tx_count();
}
//------------------------------------------------------------------
@@ -2293,6 +2323,24 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<u
return true;
}
//------------------------------------------------------------------
+void Blockchain::on_new_tx_from_block(const cryptonote::transaction &tx)
+{
+#if defined(PER_BLOCK_CHECKPOINT)
+ // check if we're doing per-block checkpointing
+ if (m_db->height() < m_blocks_hash_check.size())
+ {
+ TIME_MEASURE_START(a);
+ m_blocks_txs_check.push_back(get_transaction_hash(tx));
+ TIME_MEASURE_FINISH(a);
+ if(m_show_time_stats)
+ {
+ size_t ring_size = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() : 0;
+ MINFO("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a);
+ }
+ }
+#endif
+}
+//------------------------------------------------------------------
//FIXME: it seems this function is meant to be merely a wrapper around
// another function of the same name, this one adding one bit of
// functionality. Should probably move anything more than that
@@ -2308,19 +2356,10 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh
#if defined(PER_BLOCK_CHECKPOINT)
// check if we're doing per-block checkpointing
- // FIXME: investigate why this block returns
if (m_db->height() < m_blocks_hash_check.size() && kept_by_block)
{
- TIME_MEASURE_START(a);
- m_blocks_txs_check.push_back(get_transaction_hash(tx));
max_used_block_id = null_hash;
max_used_block_height = 0;
- TIME_MEASURE_FINISH(a);
- if(m_show_time_stats)
- {
- size_t ring_size = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() : 0;
- MINFO("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a);
- }
return true;
}
#endif
@@ -2552,6 +2591,25 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
}
}
+ // from v7, sorted ins
+ if (hf_version >= 7) {
+ const crypto::key_image *last_key_image = NULL;
+ for (size_t n = 0; n < tx.vin.size(); ++n)
+ {
+ const txin_v &txin = tx.vin[n];
+ if (txin.type() == typeid(txin_to_key))
+ {
+ const txin_to_key& in_to_key = boost::get<txin_to_key>(txin);
+ if (last_key_image && memcmp(&in_to_key.k_image, last_key_image, sizeof(*last_key_image)) >= 0)
+ {
+ MERROR_VER("transaction has unsorted inputs");
+ tvc.m_verifivation_failed = true;
+ return false;
+ }
+ last_key_image = &in_to_key.k_image;
+ }
+ }
+ }
auto it = m_check_txin_table.find(tx_prefix_hash);
if(it == m_check_txin_table.end())
{
@@ -3206,13 +3264,21 @@ leave:
if (m_db->height() < m_blocks_hash_check.size())
{
auto hash = get_block_hash(bl);
- if (memcmp(&hash, &m_blocks_hash_check[m_db->height()], sizeof(hash)) != 0)
+ const auto &expected_hash = m_blocks_hash_check[m_db->height()];
+ if (expected_hash != crypto::null_hash)
{
- MERROR_VER("Block with id is INVALID: " << id);
- bvc.m_verifivation_failed = true;
- goto leave;
+ if (memcmp(&hash, &expected_hash, sizeof(hash)) != 0)
+ {
+ MERROR_VER("Block with id is INVALID: " << id);
+ bvc.m_verifivation_failed = true;
+ goto leave;
+ }
+ fast_check = true;
+ }
+ else
+ {
+ MCINFO("verify", "No pre-validated hash at height " << m_db->height() << ", verifying fully");
}
- fast_check = true;
}
else
#endif
@@ -3276,7 +3342,7 @@ leave:
// XXX old code adds miner tx here
- int tx_index = 0;
+ size_t tx_index = 0;
// Iterate over the block's transaction hashes, grabbing each
// from the tx_pool and validating them. Each is then added
// to txs. Keys spent in each are added to <keys> by the double spend check.
@@ -3358,7 +3424,7 @@ leave:
{
// ND: if fast_check is enabled for blocks, there is no need to check
// the transaction inputs, but do some sanity checks anyway.
- if (memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0)
+ if (tx_index >= m_blocks_txs_check.size() || memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0)
{
MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs.");
//TODO: why is this done? make sure that keeping invalid blocks makes sense.
@@ -3657,6 +3723,14 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync)
m_blocks_txs_check.clear();
m_check_txin_table.clear();
+ // when we're well clear of the precomputed hashes, free the memory
+ if (!m_blocks_hash_check.empty() && m_db->height() > m_blocks_hash_check.size() + 4096)
+ {
+ MINFO("Dumping block hashes, we're now 4k past " << m_blocks_hash_check.size());
+ m_blocks_hash_check.clear();
+ m_blocks_hash_check.shrink_to_fit();
+ }
+
CRITICAL_REGION_END();
m_tx_pool.unlock();
@@ -3681,6 +3755,98 @@ void Blockchain::output_scan_worker(const uint64_t amount, const std::vector<uin
}
}
+uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes)
+{
+ // new: . . . . . X X X X X . . . . . .
+ // pre: A A A A B B B B C C C C D D D D
+
+ // easy case: height >= hashes
+ if (height >= m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP)
+ return hashes.size();
+
+ // find hashes encompassing those block
+ size_t first_index = height / HASH_OF_HASHES_STEP;
+ size_t last_index = (height + hashes.size() - 1) / HASH_OF_HASHES_STEP;
+ MDEBUG("Blocks " << height << " - " << (height + hashes.size() - 1) << " start at " << first_index << " and end at " << last_index);
+
+ // case of not enough to calculate even a single hash
+ if (first_index == last_index && hashes.size() < HASH_OF_HASHES_STEP && (height + hashes.size()) % HASH_OF_HASHES_STEP)
+ return hashes.size();
+
+ // build hashes vector to hash hashes together
+ std::vector<crypto::hash> data;
+ data.reserve(hashes.size() + HASH_OF_HASHES_STEP - 1); // may be a bit too much
+
+ // we expect height to be either equal or a bit below db height
+ bool disconnected = (height > m_db->height());
+ size_t pop;
+ if (disconnected && height % HASH_OF_HASHES_STEP)
+ {
+ ++first_index;
+ pop = HASH_OF_HASHES_STEP - height % HASH_OF_HASHES_STEP;
+ }
+ else
+ {
+ // we might need some already in the chain for the first part of the first hash
+ for (uint64_t h = first_index * HASH_OF_HASHES_STEP; h < height; ++h)
+ {
+ data.push_back(m_db->get_block_hash_from_height(h));
+ }
+ pop = 0;
+ }
+
+ // push the data to check
+ for (const auto &h: hashes)
+ {
+ if (pop)
+ --pop;
+ else
+ data.push_back(h);
+ }
+
+ // hash and check
+ uint64_t usable = first_index * HASH_OF_HASHES_STEP - height; // may start negative, but unsigned under/overflow is not UB
+ for (size_t n = first_index; n <= last_index; ++n)
+ {
+ if (n < m_blocks_hash_of_hashes.size())
+ {
+ // if the last index isn't fully filled, we can't tell if valid
+ if (data.size() < (n - first_index) * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP)
+ break;
+
+ crypto::hash hash;
+ cn_fast_hash(data.data() + (n - first_index) * HASH_OF_HASHES_STEP, HASH_OF_HASHES_STEP * sizeof(crypto::hash), hash);
+ bool valid = hash == m_blocks_hash_of_hashes[n];
+
+ // add to the known hashes array
+ if (!valid)
+ {
+ MWARNING("invalid hash for blocks " << n * HASH_OF_HASHES_STEP << " - " << (n * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP - 1));
+ break;
+ }
+
+ size_t end = n * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP;
+ for (size_t i = n * HASH_OF_HASHES_STEP; i < end; ++i)
+ {
+ CHECK_AND_ASSERT_MES(m_blocks_hash_check[i] == crypto::null_hash || m_blocks_hash_check[i] == data[i - first_index * HASH_OF_HASHES_STEP],
+ 0, "Consistency failure in m_blocks_hash_check construction");
+ m_blocks_hash_check[i] = data[i - first_index * HASH_OF_HASHES_STEP];
+ }
+ usable += HASH_OF_HASHES_STEP;
+ }
+ else
+ {
+ // if after the end of the precomputed blocks, accept anything
+ usable += HASH_OF_HASHES_STEP;
+ if (usable > hashes.size())
+ usable = hashes.size();
+ }
+ }
+ MDEBUG("usable: " << usable << " / " << hashes.size());
+ CHECK_AND_ASSERT_MES(usable < std::numeric_limits<uint64_t>::max() / 2, 0, "usable is negative");
+ return usable;
+}
+
//------------------------------------------------------------------
// ND: Speedups:
// 1. Thread long_hash computations if possible (m_max_prepare_blocks_threads = nthreads, default = 4)
@@ -3693,6 +3859,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e
MTRACE("Blockchain::" << __func__);
TIME_MEASURE_START(prepare);
bool stop_batch;
+ uint64_t bytes = 0;
// Order of locking must be:
// m_incoming_tx_lock (optional)
@@ -3714,7 +3881,15 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e
if(blocks_entry.size() == 0)
return false;
- while (!(stop_batch = m_db->batch_start(blocks_entry.size()))) {
+ for (const auto &entry : blocks_entry)
+ {
+ bytes += entry.block.size();
+ for (const auto &tx_blob : entry.txs)
+ {
+ bytes += tx_blob.size();
+ }
+ }
+ while (!(stop_batch = m_db->batch_start(blocks_entry.size(), bytes))) {
m_blockchain_lock.unlock();
m_tx_pool.unlock();
epee::misc_utils::sleep_no_w(1000);
@@ -4144,7 +4319,7 @@ void Blockchain::cancel()
}
#if defined(PER_BLOCK_CHECKPOINT)
-static const char expected_block_hashes_hash[] = "d3ca80d50661684cde0e715d46d7c19704d2e216b21ed088af9fd4ef37ed4d65";
+static const char expected_block_hashes_hash[] = "4b553162ee4e7af3c53666506591489c68560b9175e6e941dc96c89f96f0e35c";
void Blockchain::load_compiled_in_block_hashes()
{
if (m_fast_sync && get_blocks_dat_start(m_testnet) != nullptr && get_blocks_dat_size(m_testnet) > 0)
@@ -4180,16 +4355,18 @@ void Blockchain::load_compiled_in_block_hashes()
const unsigned char *p = get_blocks_dat_start(m_testnet);
const uint32_t nblocks = *p | ((*(p+1))<<8) | ((*(p+2))<<16) | ((*(p+3))<<24);
const size_t size_needed = 4 + nblocks * sizeof(crypto::hash);
- if(nblocks > 0 && nblocks > m_db->height() && get_blocks_dat_size(m_testnet) >= size_needed)
+ if(nblocks > 0 && nblocks * HASH_OF_HASHES_STEP > m_db->height() && get_blocks_dat_size(m_testnet) >= size_needed)
{
p += sizeof(uint32_t);
+ m_blocks_hash_of_hashes.reserve(nblocks);
for (uint32_t i = 0; i < nblocks; i++)
{
crypto::hash hash;
memcpy(hash.data, p, sizeof(hash.data));
p += sizeof(hash.data);
- m_blocks_hash_check.push_back(hash);
+ m_blocks_hash_of_hashes.push_back(hash);
}
+ m_blocks_hash_check.resize(m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP, crypto::null_hash);
MINFO(nblocks << " block hashes loaded");
// FIXME: clear tx_pool because the process might have been
@@ -4220,7 +4397,7 @@ void Blockchain::load_compiled_in_block_hashes()
bool Blockchain::is_within_compiled_block_hash_area(uint64_t height) const
{
#if defined(PER_BLOCK_CHECKPOINT)
- return height < m_blocks_hash_check.size();
+ return height < m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP;
#else
return false;
#endif
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index e2da535cd..f64bd35e3 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -51,7 +51,7 @@
#include "cryptonote_tx_utils.h"
#include "cryptonote_basic/verification_context.h"
#include "crypto/hash.h"
-#include "cryptonote_basic/checkpoints.h"
+#include "checkpoints/checkpoints.h"
#include "cryptonote_basic/hardfork.h"
#include "blockchain_db/blockchain_db.h"
@@ -955,12 +955,20 @@ namespace cryptonote
bool is_within_compiled_block_hash_area(uint64_t height) const;
bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); }
+ uint64_t prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes);
void lock();
void unlock();
void cancel();
+ /**
+ * @brief called when we see a tx originating from a block
+ *
+ * Used for handling txes from historical blocks in a fast way
+ */
+ void on_new_tx_from_block(const cryptonote::transaction &tx);
+
private:
// TODO: evaluate whether or not each of these typedefs are left over from blockchain_storage
@@ -995,6 +1003,7 @@ namespace cryptonote
std::unordered_map<crypto::hash, std::unordered_map<crypto::key_image, bool>> m_check_txin_table;
// SHA-3 hashes for each block and for fast pow checking
+ std::vector<crypto::hash> m_blocks_hash_of_hashes;
std::vector<crypto::hash> m_blocks_hash_check;
std::vector<crypto::hash> m_blocks_txs_check;
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 56485aedf..61f844612 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -45,7 +45,7 @@ using namespace epee;
#include "misc_language.h"
#include <csignal>
#include <p2p/net_node.h>
-#include "cryptonote_basic/checkpoints.h"
+#include "checkpoints/checkpoints.h"
#include "ringct/rctTypes.h"
#include "blockchain_db/blockchain_db.h"
#include "ringct/rctSigs.h"
@@ -183,7 +183,7 @@ namespace cryptonote
if (!m_testnet && !m_fakechain)
{
cryptonote::checkpoints checkpoints;
- if (!checkpoints.init_default_checkpoints())
+ if (!checkpoints.init_default_checkpoints(m_testnet))
{
throw std::runtime_error("Failed to initialize checkpoints");
}
@@ -211,10 +211,9 @@ namespace cryptonote
return m_blockchain_storage.get_current_blockchain_height();
}
//-----------------------------------------------------------------------------------------------
- bool core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) const
+ void core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) const
{
top_id = m_blockchain_storage.get_tail_id(height);
- return true;
}
//-----------------------------------------------------------------------------------------------
bool core::get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks, std::list<cryptonote::blobdata>& txs) const
@@ -900,6 +899,9 @@ namespace cryptonote
//-----------------------------------------------------------------------------------------------
bool core::add_new_tx(transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
{
+ if (keeped_by_block)
+ get_blockchain_storage().on_new_tx_from_block(tx);
+
if(m_mempool.have_tx(tx_hash))
{
LOG_PRINT_L2("tx " << tx_hash << "already have transaction in tx_pool");
@@ -1027,7 +1029,15 @@ namespace cryptonote
block_verification_context bvc = boost::value_initialized<block_verification_context>();
m_miner.pause();
std::list<block_complete_entry> blocks;
- blocks.push_back(get_block_complete_entry(b, m_mempool));
+ try
+ {
+ blocks.push_back(get_block_complete_entry(b, m_mempool));
+ }
+ catch (const std::exception &e)
+ {
+ m_miner.resume();
+ return false;
+ }
prepare_handle_incoming_blocks(blocks);
m_blockchain_storage.add_new_block(b, bvc);
cleanup_handle_incoming_blocks(true);
@@ -1041,7 +1051,6 @@ namespace cryptonote
{
cryptonote_connection_context exclude_context = boost::value_initialized<cryptonote_connection_context>();
NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg);
- arg.hop = 0;
arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height();
std::list<crypto::hash> missed_txs;
std::list<cryptonote::blobdata> txs;
@@ -1300,11 +1309,12 @@ namespace cryptonote
bool core::check_updates()
{
static const char software[] = "monero";
- static const char subdir[] = "cli"; // because it can never be simple
#ifdef BUILD_TAG
static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG);
+ static const char subdir[] = "cli"; // because it can never be simple
#else
static const char buildtag[] = "source";
+ static const char subdir[] = "source"; // because it can never be simple
#endif
if (check_updates_level == UPDATES_DISABLED)
@@ -1400,6 +1410,11 @@ namespace cryptonote
return m_target_blockchain_height;
}
//-----------------------------------------------------------------------------------------------
+ uint64_t core::prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes)
+ {
+ return get_blockchain_storage().prevalidate_block_hashes(height, hashes);
+ }
+ //-----------------------------------------------------------------------------------------------
std::time_t core::get_start_time() const
{
return start_time;
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index 4ea33dbe1..7340e1024 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -299,10 +299,8 @@ namespace cryptonote
*
* @param height return-by-reference height of the block
* @param top_id return-by-reference hash of the block
- *
- * @return true
*/
- bool get_blockchain_top(uint64_t& height, crypto::hash& top_id) const;
+ void get_blockchain_top(uint64_t& height, crypto::hash& top_id) const;
/**
* @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list<std::pair<cryptonote::blobdata,block>>&, std::list<transaction>&) const
@@ -752,6 +750,13 @@ namespace cryptonote
*/
bool fluffy_blocks_enabled() const { return m_fluffy_blocks_enabled; }
+ /**
+ * @brief check a set of hashes against the precompiled hash set
+ *
+ * @return number of usable blocks
+ */
+ uint64_t prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes);
+
private:
/**
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index 94f069827..96f6ee872 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -28,9 +28,11 @@
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+#include <unordered_set>
#include "include_base_utils.h"
using namespace epee;
+#include "common/apply_permutation.h"
#include "cryptonote_tx_utils.h"
#include "cryptonote_config.h"
#include "cryptonote_basic/miner.h"
@@ -156,8 +158,14 @@ namespace cryptonote
return destinations[0].addr.m_view_public_key;
}
//---------------------------------------------------------------
- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct)
+ bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct)
{
+ if (destinations.empty())
+ {
+ LOG_ERROR("The destinations must be non-empty");
+ return false;
+ }
+
std::vector<rct::key> amount_keys;
tx.set_null();
amount_keys.clear();
@@ -236,15 +244,19 @@ namespace cryptonote
in_contexts.push_back(input_generation_context_data());
keypair& in_ephemeral = in_contexts.back().in_ephemeral;
crypto::key_image img;
- if(!generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img))
+ const auto& out_key = reinterpret_cast<const crypto::public_key&>(src_entr.outputs[src_entr.real_output].second.dest);
+ if(!generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral, img))
+ {
+ LOG_ERROR("Key image generation failed!");
return false;
+ }
//check that derivated key is equal with real output key
if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) )
{
LOG_ERROR("derived public key mismatch with output public key at index " << idx << ", real out " << src_entr.real_output << "! "<< ENDL << "derived_key:"
<< string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:"
- << string_tools::pod_to_hex(src_entr.outputs[src_entr.real_output].second) );
+ << string_tools::pod_to_hex(src_entr.outputs[src_entr.real_output].second.dest) );
LOG_ERROR("amount " << src_entr.amount << ", rct " << src_entr.rct);
LOG_ERROR("tx pubkey " << src_entr.real_out_tx_key << ", real_output_in_tx_index " << src_entr.real_output_in_tx_index);
return false;
@@ -267,16 +279,99 @@ namespace cryptonote
std::vector<tx_destination_entry> shuffled_dsts(destinations);
std::random_shuffle(shuffled_dsts.begin(), shuffled_dsts.end(), [](unsigned int i) { return crypto::rand<unsigned int>() % i; });
+ // sort ins by their key image
+ std::vector<size_t> ins_order(sources.size());
+ for (size_t n = 0; n < sources.size(); ++n)
+ ins_order[n] = n;
+ std::sort(ins_order.begin(), ins_order.end(), [&](const size_t i0, const size_t i1) {
+ const txin_to_key &tk0 = boost::get<txin_to_key>(tx.vin[i0]);
+ const txin_to_key &tk1 = boost::get<txin_to_key>(tx.vin[i1]);
+ return memcmp(&tk0.k_image, &tk1.k_image, sizeof(tk0.k_image)) < 0;
+ });
+ tools::apply_permutation(ins_order, [&] (size_t i0, size_t i1) {
+ std::swap(tx.vin[i0], tx.vin[i1]);
+ std::swap(in_contexts[i0], in_contexts[i1]);
+ std::swap(sources[i0], sources[i1]);
+ });
+
+ // figure out if we need to make additional tx pubkeys
+ size_t num_stdaddresses = 0;
+ size_t num_subaddresses = 0;
+ std::unordered_set<cryptonote::account_public_address> unique_dst_addresses;
+ account_public_address single_dest_subaddress;
+ for(const tx_destination_entry& dst_entr: destinations)
+ {
+ if (change_addr && dst_entr.addr == *change_addr)
+ continue;
+ if (unique_dst_addresses.count(dst_entr.addr) == 0)
+ {
+ unique_dst_addresses.insert(dst_entr.addr);
+ if (dst_entr.is_subaddress)
+ {
+ ++num_subaddresses;
+ single_dest_subaddress = dst_entr.addr;
+ }
+ else
+ {
+ ++num_stdaddresses;
+ }
+ }
+ }
+ LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << "subaddresses");
+
+ // if this is a single-destination transfer to a subaddress, we set the tx pubkey to R=s*D
+ if (num_stdaddresses == 0 && num_subaddresses == 1)
+ {
+ txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(txkey.sec)));
+ remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key));
+ add_tx_pub_key_to_extra(tx, txkey.pub);
+ }
+
+ std::vector<crypto::public_key> additional_tx_public_keys;
+ additional_tx_keys.clear();
+
+ // we don't need to include additional tx keys if:
+ // - all the destinations are standard addresses
+ // - there's only one destination which is a subaddress
+ bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1);
+
uint64_t summary_outs_money = 0;
//fill outputs
size_t output_index = 0;
- for(const tx_destination_entry& dst_entr: shuffled_dsts)
+ for(const tx_destination_entry& dst_entr: destinations)
{
CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount);
crypto::key_derivation derivation;
crypto::public_key out_eph_public_key;
- bool r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, txkey.sec, derivation);
- CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << txkey.sec << ")");
+
+ // make additional tx pubkey if necessary
+ keypair additional_txkey;
+ if (need_additional_txkeys)
+ {
+ additional_txkey = keypair::generate();
+ if (dst_entr.is_subaddress)
+ additional_txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(dst_entr.addr.m_spend_public_key), rct::sk2rct(additional_txkey.sec)));
+ }
+
+ bool r;
+ if (change_addr && dst_entr.addr == *change_addr)
+ {
+ // sending change to yourself; derivation = a*R
+ r = crypto::generate_key_derivation(txkey.pub, sender_account_keys.m_view_secret_key, derivation);
+ CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << txkey.pub << ", " << sender_account_keys.m_view_secret_key << ")");
+ }
+ else
+ {
+ // sending to the recipient; derivation = r*A (or s*C in the subaddress scheme)
+ r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec, derivation);
+ CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << (dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec) << ")");
+ }
+
+ if (need_additional_txkeys)
+ {
+ additional_tx_public_keys.push_back(additional_txkey.pub);
+ additional_tx_keys.push_back(additional_txkey.sec);
+ }
if (tx.version > 1)
{
@@ -297,6 +392,17 @@ namespace cryptonote
summary_outs_money += dst_entr.amount;
}
+ remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys));
+ add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys);
+
+ LOG_PRINT_L2("tx pubkey: " << txkey.pub);
+ if (need_additional_txkeys)
+ {
+ LOG_PRINT_L2("additional tx pubkeys: ");
+ for (size_t i = 0; i < additional_tx_public_keys.size(); ++i)
+ LOG_PRINT_L2(additional_tx_public_keys[i]);
+ }
+
//check money
if(summary_outs_money > summary_inputs_money )
{
@@ -459,10 +565,13 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
- bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time)
+ bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time)
{
+ std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
+ subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0};
crypto::secret_key tx_key;
- return construct_tx_and_get_tx_key(sender_account_keys, sources, destinations, extra, tx, unlock_time, tx_key);
+ std::vector<crypto::secret_key> additional_tx_keys;
+ return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys);
}
//---------------------------------------------------------------
bool generate_genesis_block(
@@ -484,8 +593,9 @@ namespace cryptonote
std::string genesis_coinbase_tx_hex = config::GENESIS_TX;
blobdata tx_bl;
- string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl);
- bool r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx);
+ bool r = string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl);
+ CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob");
+ r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx);
CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob");
bl.major_version = CURRENT_BLOCK_MAJOR_VERSION;
bl.minor_version = CURRENT_BLOCK_MINOR_VERSION;
diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h
index 7aa7c280d..9a3b2484d 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.h
+++ b/src/cryptonote_core/cryptonote_tx_utils.h
@@ -46,6 +46,7 @@ namespace cryptonote
std::vector<output_entry> outputs; //index + key + optional ringct commitment
size_t real_output; //index in outputs vector of real output_entry
crypto::public_key real_out_tx_key; //incoming real tx public key
+ std::vector<crypto::public_key> real_out_additional_tx_keys; //incoming real tx additional public keys
size_t real_output_in_tx_index; //index in transaction outputs vector
uint64_t amount; //money
bool rct; //true if the output is rct
@@ -58,20 +59,22 @@ namespace cryptonote
{
uint64_t amount; //money
account_public_address addr; //destination address
+ bool is_subaddress;
- tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)) { }
- tx_destination_entry(uint64_t a, const account_public_address &ad) : amount(a), addr(ad) { }
+ tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)), is_subaddress(false) { }
+ tx_destination_entry(uint64_t a, const account_public_address &ad, bool is_subaddress) : amount(a), addr(ad), is_subaddress(is_subaddress) { }
BEGIN_SERIALIZE_OBJECT()
VARINT_FIELD(amount)
FIELD(addr)
+ FIELD(is_subaddress)
END_SERIALIZE()
};
//---------------------------------------------------------------
crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const account_keys &sender_keys);
- bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time);
- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct = false);
+ bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time);
+ bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false);
bool generate_genesis_block(
block& bl
@@ -82,6 +85,7 @@ namespace cryptonote
}
BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0)
+BOOST_CLASS_VERSION(cryptonote::tx_destination_entry, 1)
namespace boost
{
@@ -98,5 +102,15 @@ namespace boost
a & x.rct;
a & x.mask;
}
+
+ template <class Archive>
+ inline void serialize(Archive& a, cryptonote::tx_destination_entry& x, const boost::serialization::version_type ver)
+ {
+ a & x.amount;
+ a & x.addr;
+ if (ver < 1)
+ return;
+ a & x.is_subaddress;
+ }
}
}
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index 7de392036..9071c330c 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -202,6 +202,9 @@ namespace cryptonote
return false;
}
+ // assume failure during verification steps until success is certain
+ tvc.m_verifivation_failed = true;
+
time_t receive_time = time(nullptr);
crypto::hash max_used_block_id = null_hash;
@@ -246,6 +249,7 @@ namespace cryptonote
{
LOG_PRINT_L1("tx used wrong inputs, rejected");
tvc.m_verifivation_failed = true;
+ tvc.m_invalid_input = true;
return false;
}
}else
@@ -285,9 +289,6 @@ namespace cryptonote
tvc.m_should_be_relayed = true;
}
- // assume failure during verification steps until success is certain
- tvc.m_verifivation_failed = true;
-
tvc.m_verifivation_failed = false;
MINFO("Transaction added to pool: txid " << id << " bytes: " << blob_size << " fee/byte: " << (fee / (double)blob_size));
@@ -298,7 +299,8 @@ namespace cryptonote
{
crypto::hash h = null_hash;
size_t blob_size = 0;
- get_transaction_hash(tx, h, blob_size);
+ if (!get_transaction_hash(tx, h, blob_size) || blob_size == 0)
+ return false;
return add_tx(tx, h, blob_size, tvc, keeped_by_block, relayed, do_not_relay, version);
}
//---------------------------------------------------------------------------------
@@ -1090,12 +1092,13 @@ namespace cryptonote
m_txs_by_fee_and_receive_time.clear();
m_spent_key_images.clear();
- return m_blockchain.for_all_txpool_txes([this](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd) {
+ std::vector<crypto::hash> remove;
+ bool r = m_blockchain.for_all_txpool_txes([this, &remove](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd) {
cryptonote::transaction tx;
if (!parse_and_validate_tx_from_blob(*bd, tx))
{
- MERROR("Failed to parse tx from txpool");
- return false;
+ MWARNING("Failed to parse tx from txpool, removing");
+ remove.push_back(txid);
}
if (!insert_key_images(tx, meta.kept_by_block))
{
@@ -1105,6 +1108,25 @@ namespace cryptonote
m_txs_by_fee_and_receive_time.emplace(std::pair<double, time_t>(meta.fee / (double)meta.blob_size, meta.receive_time), txid);
return true;
}, true);
+ if (!r)
+ return false;
+ if (!remove.empty())
+ {
+ LockedTXN lock(m_blockchain);
+ for (const auto &txid: remove)
+ {
+ try
+ {
+ m_blockchain.remove_txpool_tx(txid);
+ }
+ catch (const std::exception &e)
+ {
+ MWARNING("Failed to remove corrupt transaction: " << txid);
+ // ignore error
+ }
+ }
+ }
+ return true;
}
//---------------------------------------------------------------------------------
diff --git a/src/cryptonote_protocol/CMakeLists.txt b/src/cryptonote_protocol/CMakeLists.txt
index 4ce380a48..347e48eee 100644
--- a/src/cryptonote_protocol/CMakeLists.txt
+++ b/src/cryptonote_protocol/CMakeLists.txt
@@ -39,5 +39,3 @@ target_link_libraries(cryptonote_protocol
p2p
PRIVATE
${EXTRA_LIBRARIES})
-add_dependencies(cryptonote_protocol
- version)
diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp
index 02a8e3ec2..813167400 100644
--- a/src/cryptonote_protocol/block_queue.cpp
+++ b/src/cryptonote_protocol/block_queue.cpp
@@ -205,8 +205,7 @@ std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_hei
bool block_queue::is_blockchain_placeholder(const span &span) const
{
- static const boost::uuids::uuid uuid0 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- return span.connection_id == uuid0;
+ return span.connection_id == boost::uuids::nil_uuid();
}
std::pair<uint64_t, uint64_t> block_queue::get_start_gap_span() const
@@ -340,7 +339,7 @@ size_t block_queue::get_num_filled_spans() const
crypto::hash block_queue::get_last_known_hash(const boost::uuids::uuid &connection_id) const
{
boost::unique_lock<boost::recursive_mutex> lock(mutex);
- crypto::hash hash = cryptonote::null_hash;
+ crypto::hash hash = crypto::null_hash;
uint64_t highest_height = 0;
for (const auto &span: blocks)
{
diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h
index 71e205c23..1804cc101 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_defs.h
+++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h
@@ -128,12 +128,10 @@ namespace cryptonote
{
block_complete_entry b;
uint64_t current_blockchain_height;
- uint32_t hop;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(b)
KV_SERIALIZE(current_blockchain_height)
- KV_SERIALIZE(hop)
END_KV_SERIALIZE_MAP()
};
};
@@ -254,12 +252,10 @@ namespace cryptonote
{
block_complete_entry b;
uint64_t current_blockchain_height;
- uint32_t hop;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(b)
KV_SERIALIZE(current_blockchain_height)
- KV_SERIALIZE(hop)
END_KV_SERIALIZE_MAP()
};
};
@@ -276,13 +272,11 @@ namespace cryptonote
crypto::hash block_hash;
uint64_t current_blockchain_height;
std::vector<size_t> missing_tx_indices;
- uint32_t hop;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_VAL_POD_AS_BLOB(block_hash)
KV_SERIALIZE(current_blockchain_height)
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(missing_tx_indices)
- KV_SERIALIZE(hop)
END_KV_SERIALIZE_MAP()
};
};
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index 803d948cc..d16e5d7e5 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -51,7 +51,7 @@
#define BLOCK_QUEUE_NBLOCKS_THRESHOLD 10 // chunks of N blocks
#define BLOCK_QUEUE_SIZE_THRESHOLD (100*1024*1024) // MB
#define REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD (5 * 1000000) // microseconds
-#define IDLE_PEER_KICK_TIME (45 * 1000000) // microseconds
+#define IDLE_PEER_KICK_TIME (600 * 1000000) // microseconds
namespace cryptonote
{
@@ -336,7 +336,7 @@ namespace cryptonote
template<class t_core>
int t_cryptonote_protocol_handler<t_core>::handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context)
{
- MLOG_P2P_MESSAGE("Received NOTIFY_NEW_BLOCK (hop " << arg.hop << ", " << arg.b.txs.size() << " txes)");
+ MLOG_P2P_MESSAGE("Received NOTIFY_NEW_BLOCK (" << 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
@@ -379,7 +379,6 @@ namespace cryptonote
}
if(bvc.m_added_to_main_chain)
{
- ++arg.hop;
//TODO: Add here announce protocol usage
relay_block(arg, context);
}else if(bvc.m_marked_as_orphaned)
@@ -397,7 +396,7 @@ namespace cryptonote
template<class t_core>
int t_cryptonote_protocol_handler<t_core>::handle_notify_new_fluffy_block(int command, NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& context)
{
- MLOG_P2P_MESSAGE("Received NOTIFY_NEW_FLUFFY_BLOCK (height " << arg.current_blockchain_height << ", hop " << arg.hop << ", " << arg.b.txs.size() << " txes)");
+ MLOG_P2P_MESSAGE("Received NOTIFY_NEW_FLUFFY_BLOCK (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
@@ -607,7 +606,6 @@ namespace cryptonote
MDEBUG(" tx " << new_block.tx_hashes[txidx]);
NOTIFY_REQUEST_FLUFFY_MISSING_TX::request missing_tx_req;
missing_tx_req.block_hash = get_block_hash(new_block);
- missing_tx_req.hop = arg.hop;
missing_tx_req.current_blockchain_height = arg.current_blockchain_height;
missing_tx_req.missing_tx_indices = std::move(need_tx_indices);
@@ -644,10 +642,8 @@ namespace cryptonote
}
if( bvc.m_added_to_main_chain )
{
- ++arg.hop;
//TODO: Add here announce protocol usage
NOTIFY_NEW_BLOCK::request reg_arg = AUTO_VAL_INIT(reg_arg);
- reg_arg.hop = arg.hop;
reg_arg.current_blockchain_height = arg.current_blockchain_height;
reg_arg.b = b;
relay_block(reg_arg, context);
@@ -700,7 +696,6 @@ namespace cryptonote
NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_response;
fluffy_response.b.block = t_serializable_object_to_blob(b);
fluffy_response.current_blockchain_height = arg.current_blockchain_height;
- fluffy_response.hop = arg.hop;
for(auto& tx_idx: arg.missing_tx_indices)
{
if(tx_idx < b.tx_hashes.size())
@@ -936,7 +931,8 @@ namespace cryptonote
}
// get the last parsed block, which should be the highest one
- if(m_core.have_block(cryptonote::get_block_hash(b)))
+ const crypto::hash last_block_hash = cryptonote::get_block_hash(b);
+ if(m_core.have_block(last_block_hash))
{
const uint64_t subchain_height = start_height + arg.blocks.size();
LOG_DEBUG_CC(context, "These are old blocks, ignoring: blocks " << start_height << " - " << (subchain_height-1) << ", blockchain height " << m_core.get_current_blockchain_height());
@@ -954,7 +950,7 @@ namespace cryptonote
MDEBUG(context << " adding span: " << arg.blocks.size() << " at height " << start_height << ", " << dt.total_microseconds()/1e6 << " seconds, " << (rate/1e3) << " kB/s, size now " << (m_block_queue.get_data_size() + blocks_size) / 1048576.f << " MB");
m_block_queue.add_blocks(start_height, arg.blocks, context.m_connection_id, rate, blocks_size);
- context.m_last_known_hash = cryptonote::get_blob_hash(arg.blocks.back().block);
+ context.m_last_known_hash = last_block_hash;
if (!m_core.get_test_drop_download() || !m_core.get_test_drop_download_height()) { // DISCARD BLOCKS for testing
return 1;
@@ -1185,6 +1181,7 @@ skip:
bool t_cryptonote_protocol_handler<t_core>::kick_idle_peers()
{
MTRACE("Checking for idle peers...");
+ std::vector<boost::uuids::uuid> kick_connections;
m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool
{
if (context.m_state == cryptonote_connection_context::state_synchronizing)
@@ -1194,12 +1191,18 @@ skip:
if (dt.total_microseconds() > IDLE_PEER_KICK_TIME)
{
MINFO(context << " kicking idle peer");
- ++context.m_callback_request_count;
- m_p2p->request_callback(context);
+ kick_connections.push_back(context.m_connection_id);
}
}
return true;
});
+ for (const boost::uuids::uuid &conn_id: kick_connections)
+ {
+ m_p2p->for_connection(conn_id, [this](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) {
+ drop_connection(context, false, false);
+ return true;
+ });
+ }
return true;
}
//------------------------------------------------------------------------------------------------------------------------
@@ -1488,7 +1491,7 @@ skip:
if (!start_from_current_chain)
{
// 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 != cryptonote::null_hash && r.block_ids.front() != context.m_last_known_hash)
+ if (context.m_last_known_hash != crypto::null_hash && r.block_ids.front() != context.m_last_known_hash)
r.block_ids.push_front(context.m_last_known_hash);
}
@@ -1583,10 +1586,22 @@ skip:
return 1;
}
+ uint64_t n_use_blocks = m_core.prevalidate_block_hashes(arg.start_height, arg.m_block_ids);
+ if (n_use_blocks == 0)
+ {
+ LOG_ERROR_CCONTEXT("Peer yielded no usable blocks, dropping connection");
+ drop_connection(context, false, false);
+ return 1;
+ }
+
+ uint64_t added = 0;
for(auto& bl_id: arg.m_block_ids)
{
context.m_needed_objects.push_back(bl_id);
+ if (++added == n_use_blocks)
+ break;
}
+ context.m_last_response_height -= arg.m_block_ids.size() - n_use_blocks;
if (!request_missing_objects(context, false))
{
@@ -1605,7 +1620,6 @@ skip:
bool t_cryptonote_protocol_handler<t_core>::relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context)
{
NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_arg = AUTO_VAL_INIT(fluffy_arg);
- fluffy_arg.hop = arg.hop;
fluffy_arg.current_blockchain_height = arg.current_blockchain_height;
std::list<blobdata> fluffy_txs;
fluffy_arg.b = arg.b;
@@ -1618,7 +1632,7 @@ skip:
// sort peers between fluffy ones and others
std::list<boost::uuids::uuid> fullConnections, fluffyConnections;
- m_p2p->for_each_connection([this, &arg, &fluffy_arg, &exclude_context, &fullConnections, &fluffyConnections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)
+ m_p2p->for_each_connection([this, &exclude_context, &fullConnections, &fluffyConnections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)
{
if (peer_id && exclude_context.m_connection_id != context.m_connection_id)
{
diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt
index 782667867..d0fc1d846 100644
--- a/src/daemon/CMakeLists.txt
+++ b/src/daemon/CMakeLists.txt
@@ -94,6 +94,7 @@ target_link_libraries(daemon
daemonizer
serialization
daemon_rpc_server
+ version
${Boost_CHRONO_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY}
@@ -102,7 +103,6 @@ target_link_libraries(daemon
${CMAKE_THREAD_LIBS_INIT}
${ZMQ_LIB}
${EXTRA_LIBRARIES})
-add_dependencies(daemon version)
set_property(TARGET daemon
PROPERTY
OUTPUT_NAME "monerod")
diff --git a/src/daemon/command_line_args.h b/src/daemon/command_line_args.h
index f19d5cc63..7fa58f9d8 100644
--- a/src/daemon/command_line_args.h
+++ b/src/daemon/command_line_args.h
@@ -46,6 +46,11 @@ namespace daemon_args
, "Specify log file"
, ""
};
+ const command_line::arg_descriptor<std::size_t> arg_max_log_file_size = {
+ "max-log-file-size"
+ , "Specify maximum log file size [B]"
+ , MAX_LOG_FILE_SIZE
+ };
const command_line::arg_descriptor<std::string> arg_log_level = {
"log-level"
, ""
diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp
index d949a57b1..c85e5edb5 100644
--- a/src/daemon/command_parser_executor.cpp
+++ b/src/daemon/command_parser_executor.cpp
@@ -125,12 +125,17 @@ bool t_command_parser_executor::print_blockchain_info(const std::vector<std::str
bool t_command_parser_executor::set_log_level(const std::vector<std::string>& args)
{
- if(args.size() != 1)
+ if(args.size() > 1)
{
std::cout << "use: set_log [<log_level_number_0-4> | <categories>]" << std::endl;
return true;
}
+ if (args.empty())
+ {
+ return m_executor.set_log_categories("+");
+ }
+
uint16_t l = 0;
if(epee::string_tools::get_xtype_from_string(l, args[0]))
{
@@ -247,20 +252,18 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg
return true;
}
- cryptonote::account_public_address adr;
- bool has_payment_id;
- crypto::hash8 payment_id;
+ cryptonote::address_parse_info info;
bool testnet = false;
- if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, args.front()))
+ if(!cryptonote::get_account_address_from_str(info, false, args.front()))
{
- if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, args.front()))
+ if(!cryptonote::get_account_address_from_str(info, true, args.front()))
{
bool dnssec_valid;
std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(args.front(), dnssec_valid,
[](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid){return addresses[0];});
- if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, address_str))
+ if(!cryptonote::get_account_address_from_str(info, false, address_str))
{
- if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, address_str))
+ if(!cryptonote::get_account_address_from_str(info, true, address_str))
{
std::cout << "target account address has wrong format" << std::endl;
return true;
@@ -276,6 +279,11 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg
testnet = true;
}
}
+ if (info.is_subaddress)
+ {
+ tools::fail_msg_writer() << "subaddress for mining reward is not yet supported!" << std::endl;
+ return true;
+ }
if(testnet)
std::cout << "Mining to a testnet address, make sure this is intentional!" << std::endl;
uint64_t threads_count = 1;
@@ -302,7 +310,7 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg
threads_count = (ok && 0 < threads_count) ? threads_count : 1;
}
- m_executor.start_mining(adr, threads_count, testnet, do_background_mining, ignore_battery);
+ m_executor.start_mining(info.address, threads_count, testnet, do_background_mining, ignore_battery);
return true;
}
@@ -334,17 +342,18 @@ bool t_command_parser_executor::set_limit(const std::vector<std::string>& args)
if(args.size()==0) {
return m_executor.get_limit();
}
- int limit;
+ int64_t limit;
try {
- limit = std::stoi(args[0]);
+ limit = std::stoll(args[0]);
}
- catch(std::invalid_argument& ex) {
+ catch(const std::exception& ex) {
+ std::cout << "failed to parse argument" << std::endl;
return false;
}
- if (limit==-1) limit=128;
- limit *= 1024;
+ if (limit > 0)
+ limit *= 1024;
- return m_executor.set_limit(limit);
+ return m_executor.set_limit(limit, limit);
}
bool t_command_parser_executor::set_limit_up(const std::vector<std::string>& args)
@@ -353,17 +362,18 @@ bool t_command_parser_executor::set_limit_up(const std::vector<std::string>& arg
if(args.size()==0) {
return m_executor.get_limit_up();
}
- int limit;
+ int64_t limit;
try {
- limit = std::stoi(args[0]);
+ limit = std::stoll(args[0]);
}
- catch(std::invalid_argument& ex) {
+ catch(const std::exception& ex) {
+ std::cout << "failed to parse argument" << std::endl;
return false;
}
- if (limit==-1) limit=128;
- limit *= 1024;
+ if (limit > 0)
+ limit *= 1024;
- return m_executor.set_limit_up(limit);
+ return m_executor.set_limit(0, limit);
}
bool t_command_parser_executor::set_limit_down(const std::vector<std::string>& args)
@@ -372,17 +382,18 @@ bool t_command_parser_executor::set_limit_down(const std::vector<std::string>& a
if(args.size()==0) {
return m_executor.get_limit_down();
}
- int limit;
+ int64_t limit;
try {
- limit = std::stoi(args[0]);
+ limit = std::stoll(args[0]);
}
- catch(std::invalid_argument& ex) {
+ catch(const std::exception& ex) {
+ std::cout << "failed to parse argument" << std::endl;
return false;
}
- if (limit==-1) limit=128;
- limit *= 1024;
+ if (limit > 0)
+ limit *= 1024;
- return m_executor.set_limit_down(limit);
+ return m_executor.set_limit(limit, 0);
}
bool t_command_parser_executor::out_peers(const std::vector<std::string>& args)
@@ -394,7 +405,7 @@ bool t_command_parser_executor::out_peers(const std::vector<std::string>& args)
limit = std::stoi(args[0]);
}
- catch(std::invalid_argument& ex) {
+ catch(std::exception& ex) {
_erro("stoi exception");
return false;
}
@@ -424,7 +435,7 @@ bool t_command_parser_executor::hard_fork_info(const std::vector<std::string>& a
try {
version = std::stoi(args[0]);
}
- catch(std::invalid_argument& ex) {
+ catch(std::exception& ex) {
return false;
}
if (version <= 0 || version > 255)
@@ -449,7 +460,14 @@ bool t_command_parser_executor::ban(const std::vector<std::string>& args)
time_t seconds = P2P_IP_BLOCKTIME;
if (args.size() > 1)
{
- seconds = std::stoi(args[1]);
+ try
+ {
+ seconds = std::stoi(args[1]);
+ }
+ catch (const std::exception &e)
+ {
+ return false;
+ }
if (seconds == 0)
{
return false;
@@ -485,20 +503,34 @@ bool t_command_parser_executor::flush_txpool(const std::vector<std::string>& arg
bool t_command_parser_executor::output_histogram(const std::vector<std::string>& args)
{
- if (args.size() > 2) return false;
-
+ std::vector<uint64_t> amounts;
uint64_t min_count = 3;
uint64_t max_count = 0;
+ size_t n_raw = 0;
- if (args.size() >= 1)
- {
- min_count = boost::lexical_cast<uint64_t>(args[0]);
- }
- if (args.size() >= 2)
+ for (size_t n = 0; n < args.size(); ++n)
{
- max_count = boost::lexical_cast<uint64_t>(args[1]);
+ if (args[n][0] == '@')
+ {
+ amounts.push_back(boost::lexical_cast<uint64_t>(args[n].c_str() + 1));
+ }
+ else if (n_raw == 0)
+ {
+ min_count = boost::lexical_cast<uint64_t>(args[n]);
+ n_raw++;
+ }
+ else if (n_raw == 1)
+ {
+ max_count = boost::lexical_cast<uint64_t>(args[n]);
+ n_raw++;
+ }
+ else
+ {
+ std::cout << "Invalid syntax: more than two non-amount parameters" << std::endl;
+ return true;
+ }
}
- return m_executor.output_histogram(min_count, max_count);
+ return m_executor.output_histogram(amounts, min_count, max_count);
}
bool t_command_parser_executor::print_coinbase_tx_sum(const std::vector<std::string>& args)
diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h
index f301ef14a..6443d9be0 100644
--- a/src/daemon/command_parser_executor.h
+++ b/src/daemon/command_parser_executor.h
@@ -36,7 +36,7 @@
#pragma once
-#include <boost/optional/optional_fwd.hpp>
+#include <boost/optional/optional.hpp>
#include "daemon/rpc_command_executor.h"
#include "common/common_fwd.h"
diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp
index 9df698547..3f1543857 100644
--- a/src/daemon/command_server.cpp
+++ b/src/daemon/command_server.cpp
@@ -141,7 +141,7 @@ t_command_server::t_command_server(
m_command_lookup.set_handler(
"set_log"
, std::bind(&t_command_parser_executor::set_log_level, &m_parser, p::_1)
- , "set_log <level>|<categories> - Change current loglevel, <level> is a number 0-4"
+ , "set_log <level>|<{+,-,}categories> - Change current log level/categories, <level> is a number 0-4"
);
m_command_lookup.set_handler(
"diff"
@@ -241,7 +241,7 @@ t_command_server::t_command_server(
m_command_lookup.set_handler(
"bc_dyn_stats"
, std::bind(&t_command_parser_executor::print_blockchain_dynamic_stats, &m_parser, p::_1)
- , "Print information about current blockchain dynamic state"
+ , "Print information about current blockchain dynamic state, bc_dyn_stats <last n blocks>"
);
m_command_lookup.set_handler(
"update"
diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp
index 8f6d542b6..5d548f410 100644
--- a/src/daemon/main.cpp
+++ b/src/daemon/main.cpp
@@ -61,7 +61,7 @@ int main(int argc, char const * argv[])
// TODO parse the debug options like set log level right here at start
- tools::sanitize_locale();
+ tools::on_startup();
epee::string_tools::set_module_name_and_folder(argv[0]);
@@ -88,6 +88,7 @@ int main(int argc, char const * argv[])
bf::path default_log = default_data_dir / std::string(CRYPTONOTE_NAME ".log");
command_line::add_arg(core_settings, daemon_args::arg_log_file, default_log.string());
command_line::add_arg(core_settings, daemon_args::arg_log_level);
+ command_line::add_arg(core_settings, daemon_args::arg_max_log_file_size);
command_line::add_arg(core_settings, daemon_args::arg_max_concurrency);
command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_ip);
command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_port);
@@ -204,13 +205,13 @@ int main(int argc, char const * argv[])
// absolute path
// relative path: relative to data_dir
bf::path log_file_path {data_dir / std::string(CRYPTONOTE_NAME ".log")};
- if (! vm["log-file"].defaulted())
+ if (!command_line::is_arg_defaulted(vm, daemon_args::arg_log_file))
log_file_path = command_line::get_arg(vm, daemon_args::arg_log_file);
log_file_path = bf::absolute(log_file_path, relative_path_base);
- mlog_configure(log_file_path.string(), true);
+ mlog_configure(log_file_path.string(), true, command_line::get_arg(vm, daemon_args::arg_max_log_file_size));
// Set log level
- if (!vm["log-level"].defaulted())
+ if (!command_line::is_arg_defaulted(vm, daemon_args::arg_log_level))
{
mlog_set_log(command_line::get_arg(vm, daemon_args::arg_log_level).c_str());
}
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index cda6f3f95..867c523c9 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -599,7 +599,7 @@ bool t_rpc_command_executor::set_log_categories(const std::string &categories) {
}
}
- tools::success_msg_writer() << "Log categories are now " << categories;
+ tools::success_msg_writer() << "Log categories are now " << res.categories;
return true;
}
@@ -700,6 +700,7 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash) {
std::string fail_message = "Problem fetching transaction";
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(transaction_hash));
+ req.decode_as_json = false;
if (m_is_rpc)
{
if (!m_rpc_client->rpc_request(req, res, "/gettransactions", fail_message.c_str()))
@@ -782,7 +783,7 @@ bool t_rpc_command_executor::is_key_image_spent(const crypto::key_image &ki) {
if (1 == res.spent_status.size())
{
// first as hex
- tools::success_msg_writer() << ki << ": " << (res.spent_status.front() ? "spent" : "unspent");
+ tools::success_msg_writer() << ki << ": " << (res.spent_status.front() ? "spent" : "unspent") << (res.spent_status.front() == cryptonote::COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_POOL ? " (in pool)" : "");
}
else
{
@@ -1013,7 +1014,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() {
bool t_rpc_command_executor::start_mining(cryptonote::account_public_address address, uint64_t num_threads, bool testnet, bool do_background_mining, bool ignore_battery) {
cryptonote::COMMAND_RPC_START_MINING::request req;
cryptonote::COMMAND_RPC_START_MINING::response res;
- req.miner_address = cryptonote::get_account_address_as_str(testnet, address);
+ req.miner_address = cryptonote::get_account_address_as_str(testnet, false, address);
req.threads_count = num_threads;
req.do_background_mining = do_background_mining;
req.ignore_battery = ignore_battery;
@@ -1129,48 +1130,115 @@ bool t_rpc_command_executor::print_status()
bool t_rpc_command_executor::get_limit()
{
- int limit_down = epee::net_utils::connection_basic::get_rate_down_limit( );
- int limit_up = epee::net_utils::connection_basic::get_rate_up_limit( );
- std::cout << "limit-down is " << limit_down/1024 << " kB/s" << std::endl;
- std::cout << "limit-up is " << limit_up/1024 << " kB/s" << std::endl;
- return true;
+ cryptonote::COMMAND_RPC_GET_LIMIT::request req;
+ cryptonote::COMMAND_RPC_GET_LIMIT::response res;
+
+ std::string failure_message = "Couldn't get limit";
+
+ if (m_is_rpc)
+ {
+ if (!m_rpc_client->rpc_request(req, res, "/get_limit", failure_message.c_str()))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (!m_rpc_server->on_get_limit(req, res) || res.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << make_error(failure_message, res.status);
+ return true;
+ }
+ }
+
+ tools::msg_writer() << "limit-down is " << res.limit_down/1024 << " kB/s";
+ tools::msg_writer() << "limit-up is " << res.limit_up/1024 << " kB/s";
+ return true;
}
-bool t_rpc_command_executor::set_limit(int limit)
+bool t_rpc_command_executor::set_limit(int64_t limit_down, int64_t limit_up)
{
- epee::net_utils::connection_basic::set_rate_down_limit( limit );
- epee::net_utils::connection_basic::set_rate_up_limit( limit );
- std::cout << "Set limit-down to " << limit/1024 << " kB/s" << std::endl;
- std::cout << "Set limit-up to " << limit/1024 << " kB/s" << std::endl;
- return true;
+ cryptonote::COMMAND_RPC_SET_LIMIT::request req;
+ cryptonote::COMMAND_RPC_SET_LIMIT::response res;
+
+ req.limit_down = limit_down;
+ req.limit_up = limit_up;
+
+ std::string failure_message = "Couldn't set limit";
+
+ if (m_is_rpc)
+ {
+ if (!m_rpc_client->rpc_request(req, res, "/set_limit", failure_message.c_str()))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (!m_rpc_server->on_set_limit(req, res) || res.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << make_error(failure_message, res.status);
+ return true;
+ }
+ }
+
+ tools::msg_writer() << "Set limit-down to " << res.limit_down/1024 << " kB/s";
+ tools::msg_writer() << "Set limit-up to " << res.limit_up/1024 << " kB/s";
+ return true;
}
bool t_rpc_command_executor::get_limit_up()
{
- int limit_up = epee::net_utils::connection_basic::get_rate_up_limit( );
- std::cout << "limit-up is " << limit_up/1024 << " kB/s" << std::endl;
- return true;
-}
+ cryptonote::COMMAND_RPC_GET_LIMIT::request req;
+ cryptonote::COMMAND_RPC_GET_LIMIT::response res;
-bool t_rpc_command_executor::set_limit_up(int limit)
-{
- epee::net_utils::connection_basic::set_rate_up_limit( limit );
- std::cout << "Set limit-up to " << limit/1024 << " kB/s" << std::endl;
- return true;
+ std::string failure_message = "Couldn't get limit";
+
+ if (m_is_rpc)
+ {
+ if (!m_rpc_client->rpc_request(req, res, "/get_limit", failure_message.c_str()))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (!m_rpc_server->on_get_limit(req, res) || res.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << make_error(failure_message, res.status);
+ return true;
+ }
+ }
+
+ tools::msg_writer() << "limit-up is " << res.limit_up/1024 << " kB/s";
+ return true;
}
bool t_rpc_command_executor::get_limit_down()
{
- int limit_down = epee::net_utils::connection_basic::get_rate_down_limit( );
- std::cout << "limit-down is " << limit_down/1024 << " kB/s" << std::endl;
- return true;
-}
+ cryptonote::COMMAND_RPC_GET_LIMIT::request req;
+ cryptonote::COMMAND_RPC_GET_LIMIT::response res;
-bool t_rpc_command_executor::set_limit_down(int limit)
-{
- epee::net_utils::connection_basic::set_rate_down_limit( limit );
- std::cout << "Set limit-down to " << limit/1024 << " kB/s" << std::endl;
- return true;
+ std::string failure_message = "Couldn't get limit";
+
+ if (m_is_rpc)
+ {
+ if (!m_rpc_client->rpc_request(req, res, "/get_limit", failure_message.c_str()))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (!m_rpc_server->on_get_limit(req, res) || res.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << make_error(failure_message, res.status);
+ return true;
+ }
+ }
+
+ tools::msg_writer() << "limit-down is " << res.limit_down/1024 << " kB/s";
+ return true;
}
bool t_rpc_command_executor::out_peers(uint64_t limit)
@@ -1422,13 +1490,14 @@ bool t_rpc_command_executor::flush_txpool(const std::string &txid)
return true;
}
-bool t_rpc_command_executor::output_histogram(uint64_t min_count, uint64_t max_count)
+bool t_rpc_command_executor::output_histogram(const std::vector<uint64_t> &amounts, uint64_t min_count, uint64_t max_count)
{
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req;
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response res;
std::string fail_message = "Unsuccessful";
epee::json_rpc::error error_resp;
+ req.amounts = amounts;
req.min_count = min_count;
req.max_count = max_count;
req.unlocked = false;
diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h
index fc0b39654..c9d0ac671 100644
--- a/src/daemon/rpc_command_executor.h
+++ b/src/daemon/rpc_command_executor.h
@@ -119,11 +119,7 @@ public:
bool get_limit_down();
- bool set_limit(int limit);
-
- bool set_limit_up(int limit);
-
- bool set_limit_down(int limit);
+ bool set_limit(int64_t limit_down, int64_t limit_up);
bool out_peers(uint64_t limit);
@@ -141,7 +137,7 @@ public:
bool flush_txpool(const std::string &txid);
- bool output_histogram(uint64_t min_count, uint64_t max_count);
+ bool output_histogram(const std::vector<uint64_t> &amounts, uint64_t min_count, uint64_t max_count);
bool print_coinbase_tx_sum(uint64_t height, uint64_t count);
diff --git a/src/debug_utilities/CMakeLists.txt b/src/debug_utilities/CMakeLists.txt
index 99198dc57..7a82c12d9 100644
--- a/src/debug_utilities/CMakeLists.txt
+++ b/src/debug_utilities/CMakeLists.txt
@@ -42,8 +42,6 @@ target_link_libraries(cn_deserialize
epee
${CMAKE_THREAD_LIBS_INIT})
-add_dependencies(cn_deserialize
- version)
set_property(TARGET cn_deserialize
PROPERTY
OUTPUT_NAME "monero-utils-deserialize")
@@ -65,8 +63,6 @@ target_link_libraries(object_sizes
epee
${CMAKE_THREAD_LIBS_INIT})
-add_dependencies(object_sizes
- version)
set_property(TARGET object_sizes
PROPERTY
OUTPUT_NAME "monero-utils-object-sizes")
diff --git a/src/debug_utilities/cn_deserialize.cpp b/src/debug_utilities/cn_deserialize.cpp
index a1b569554..7235ef855 100644
--- a/src/debug_utilities/cn_deserialize.cpp
+++ b/src/debug_utilities/cn_deserialize.cpp
@@ -45,7 +45,7 @@ int main(int argc, char* argv[])
uint32_t log_level = 0;
std::string input;
- tools::sanitize_locale();
+ tools::on_startup();
boost::filesystem::path output_file_path;
diff --git a/src/debug_utilities/object_sizes.cpp b/src/debug_utilities/object_sizes.cpp
index 47ba5cf6c..e58da7395 100644
--- a/src/debug_utilities/object_sizes.cpp
+++ b/src/debug_utilities/object_sizes.cpp
@@ -64,7 +64,7 @@ int main(int argc, char* argv[])
{
size_logger sl;
- tools::sanitize_locale();
+ tools::on_startup();
mlog_configure("", true);
@@ -84,7 +84,6 @@ int main(int argc, char* argv[])
SL(cryptonote::txpool_tx_meta_t);
- SL(epee::net_utils::network_address_base);
SL(epee::net_utils::ipv4_network_address);
SL(epee::net_utils::network_address);
SL(epee::net_utils::connection_context_base);
diff --git a/src/mnemonics/CMakeLists.txt b/src/mnemonics/CMakeLists.txt
index 942b6eca3..5ce2198ae 100644
--- a/src/mnemonics/CMakeLists.txt
+++ b/src/mnemonics/CMakeLists.txt
@@ -46,7 +46,8 @@ set(mnemonics_private_headers
russian.h
singleton.h
spanish.h
- esperanto.h)
+ esperanto.h
+ lojban.h)
monero_private_headers(mnemonics
${mnemonics_private_headers})
diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp
index 2fe5d9985..6ae33a743 100644
--- a/src/mnemonics/electrum-words.cpp
+++ b/src/mnemonics/electrum-words.cpp
@@ -62,6 +62,7 @@
#include "japanese.h"
#include "russian.h"
#include "esperanto.h"
+#include "lojban.h"
#include "english_old.h"
#include "language_base.h"
#include "singleton.h"
@@ -97,6 +98,7 @@ namespace
Language::Singleton<Language::Japanese>::instance(),
Language::Singleton<Language::Russian>::instance(),
Language::Singleton<Language::Esperanto>::instance(),
+ Language::Singleton<Language::Lojban>::instance(),
Language::Singleton<Language::EnglishOld>::instance()
});
Language::Base *fallback = NULL;
@@ -360,6 +362,10 @@ namespace crypto
{
language = Language::Singleton<Language::Esperanto>::instance();
}
+ else if (language_name == "Lojban")
+ {
+ language = Language::Singleton<Language::Lojban>::instance();
+ }
else
{
return false;
@@ -415,7 +421,8 @@ namespace crypto
Language::Singleton<Language::Russian>::instance(),
Language::Singleton<Language::Japanese>::instance(),
Language::Singleton<Language::Chinese_Simplified>::instance(),
- Language::Singleton<Language::Esperanto>::instance()
+ Language::Singleton<Language::Esperanto>::instance(),
+ Language::Singleton<Language::Lojban>::instance()
});
for (std::vector<Language::Base*>::iterator it = language_instances.begin();
it != language_instances.end(); it++)
diff --git a/src/mnemonics/lojban.h b/src/mnemonics/lojban.h
new file mode 100644
index 000000000..8ea9510a3
--- /dev/null
+++ b/src/mnemonics/lojban.h
@@ -0,0 +1,1693 @@
+// Copyright (c) 2014-2017, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/*!
+ * \file lojban.h
+ *
+ * \brief New Lojban word list and map.
+ */
+
+/*
+ * Word list authored by: sorpaas
+ * Sources:
+ * lo gimste jo'u lo ma'oste (http://guskant.github.io/lojbo/gismu-cmavo.html)
+ * N-grams of Lojban corpus (https://mw.lojban.org/papri/N-grams_of_Lojban_corpus)
+ */
+
+#ifndef LOJBAN_H
+#define LOJBAN_H
+
+#include <vector>
+#include <unordered_map>
+#include "language_base.h"
+#include <string>
+
+/*!
+ * \namespace Language
+ * \brief Mnemonic language related namespace.
+ */
+namespace Language
+{
+ class Lojban: public Base
+ {
+ public:
+ Lojban(): Base("Lojban", std::vector<std::string>({
+ "backi",
+ "bacru",
+ "badna",
+ "badri",
+ "bajra",
+ "bakfu",
+ "bakni",
+ "bakri",
+ "baktu",
+ "balji",
+ "balni",
+ "balre",
+ "balvi",
+ "bambu",
+ "bancu",
+ "bandu",
+ "banfi",
+ "bangu",
+ "banli",
+ "banro",
+ "banxa",
+ "banzu",
+ "bapli",
+ "barda",
+ "bargu",
+ "barja",
+ "barna",
+ "bartu",
+ "basfa",
+ "basna",
+ "basti",
+ "batci",
+ "batke",
+ "bavmi",
+ "baxso",
+ "bebna",
+ "bekpi",
+ "bemro",
+ "bende",
+ "bengo",
+ "benji",
+ "benre",
+ "benzo",
+ "bergu",
+ "bersa",
+ "berti",
+ "besna",
+ "besto",
+ "betfu",
+ "betri",
+ "bevri",
+ "bidju",
+ "bifce",
+ "bikla",
+ "bilga",
+ "bilma",
+ "bilni",
+ "bindo",
+ "binra",
+ "binxo",
+ "birje",
+ "birka",
+ "birti",
+ "bisli",
+ "bitmu",
+ "bitni",
+ "blabi",
+ "blaci",
+ "blanu",
+ "bliku",
+ "bloti",
+ "bolci",
+ "bongu",
+ "boske",
+ "botpi",
+ "boxfo",
+ "boxna",
+ "bradi",
+ "brano",
+ "bratu",
+ "brazo",
+ "bredi",
+ "bridi",
+ "brife",
+ "briju",
+ "brito",
+ "brivo",
+ "broda",
+ "bruna",
+ "budjo",
+ "bukpu",
+ "bumru",
+ "bunda",
+ "bunre",
+ "burcu",
+ "burna",
+ "cabna",
+ "cabra",
+ "cacra",
+ "cadga",
+ "cadzu",
+ "cafne",
+ "cagna",
+ "cakla",
+ "calku",
+ "calse",
+ "canci",
+ "cando",
+ "cange",
+ "canja",
+ "canko",
+ "canlu",
+ "canpa",
+ "canre",
+ "canti",
+ "carce",
+ "carfu",
+ "carmi",
+ "carna",
+ "cartu",
+ "carvi",
+ "casnu",
+ "catke",
+ "catlu",
+ "catni",
+ "catra",
+ "caxno",
+ "cecla",
+ "cecmu",
+ "cedra",
+ "cenba",
+ "censa",
+ "centi",
+ "cerda",
+ "cerni",
+ "certu",
+ "cevni",
+ "cfale",
+ "cfari",
+ "cfika",
+ "cfila",
+ "cfine",
+ "cfipu",
+ "ciblu",
+ "cicna",
+ "cidja",
+ "cidni",
+ "cidro",
+ "cifnu",
+ "cigla",
+ "cikna",
+ "cikre",
+ "ciksi",
+ "cilce",
+ "cilfu",
+ "cilmo",
+ "cilre",
+ "cilta",
+ "cimde",
+ "cimni",
+ "cinba",
+ "cindu",
+ "cinfo",
+ "cinje",
+ "cinki",
+ "cinla",
+ "cinmo",
+ "cinri",
+ "cinse",
+ "cinta",
+ "cinza",
+ "cipni",
+ "cipra",
+ "cirko",
+ "cirla",
+ "ciska",
+ "cisma",
+ "cisni",
+ "ciste",
+ "citka",
+ "citno",
+ "citri",
+ "citsi",
+ "civla",
+ "cizra",
+ "ckabu",
+ "ckafi",
+ "ckaji",
+ "ckana",
+ "ckape",
+ "ckasu",
+ "ckeji",
+ "ckiku",
+ "ckilu",
+ "ckini",
+ "ckire",
+ "ckule",
+ "ckunu",
+ "cladu",
+ "clani",
+ "claxu",
+ "cletu",
+ "clika",
+ "clinu",
+ "clira",
+ "clite",
+ "cliva",
+ "clupa",
+ "cmaci",
+ "cmalu",
+ "cmana",
+ "cmavo",
+ "cmene",
+ "cmeta",
+ "cmevo",
+ "cmila",
+ "cmima",
+ "cmoni",
+ "cnano",
+ "cnebo",
+ "cnemu",
+ "cnici",
+ "cnino",
+ "cnisa",
+ "cnita",
+ "cokcu",
+ "condi",
+ "conka",
+ "corci",
+ "cortu",
+ "cpacu",
+ "cpana",
+ "cpare",
+ "cpedu",
+ "cpina",
+ "cradi",
+ "crane",
+ "creka",
+ "crepu",
+ "cribe",
+ "crida",
+ "crino",
+ "cripu",
+ "crisa",
+ "critu",
+ "ctaru",
+ "ctebi",
+ "cteki",
+ "ctile",
+ "ctino",
+ "ctuca",
+ "cukla",
+ "cukre",
+ "cukta",
+ "culno",
+ "cumki",
+ "cumla",
+ "cunmi",
+ "cunso",
+ "cuntu",
+ "cupra",
+ "curmi",
+ "curnu",
+ "curve",
+ "cusku",
+ "cusna",
+ "cutci",
+ "cutne",
+ "cuxna",
+ "dacru",
+ "dacti",
+ "dadjo",
+ "dakfu",
+ "dakli",
+ "damba",
+ "damri",
+ "dandu",
+ "danfu",
+ "danlu",
+ "danmo",
+ "danre",
+ "dansu",
+ "danti",
+ "daplu",
+ "dapma",
+ "darca",
+ "dargu",
+ "darlu",
+ "darno",
+ "darsi",
+ "darxi",
+ "daski",
+ "dasni",
+ "daspo",
+ "dasri",
+ "datka",
+ "datni",
+ "datro",
+ "decti",
+ "degji",
+ "dejni",
+ "dekpu",
+ "dekto",
+ "delno",
+ "dembi",
+ "denci",
+ "denmi",
+ "denpa",
+ "dertu",
+ "derxi",
+ "desku",
+ "detri",
+ "dicma",
+ "dicra",
+ "didni",
+ "digno",
+ "dikca",
+ "diklo",
+ "dikni",
+ "dilcu",
+ "dilma",
+ "dilnu",
+ "dimna",
+ "dindi",
+ "dinju",
+ "dinko",
+ "dinso",
+ "dirba",
+ "dirce",
+ "dirgo",
+ "disko",
+ "ditcu",
+ "divzi",
+ "dizlo",
+ "djacu",
+ "djedi",
+ "djica",
+ "djine",
+ "djuno",
+ "donri",
+ "dotco",
+ "draci",
+ "drani",
+ "drata",
+ "drudi",
+ "dugri",
+ "dukse",
+ "dukti",
+ "dunda",
+ "dunja",
+ "dunku",
+ "dunli",
+ "dunra",
+ "dutso",
+ "dzena",
+ "dzipo",
+ "facki",
+ "fadni",
+ "fagri",
+ "falnu",
+ "famti",
+ "fancu",
+ "fange",
+ "fanmo",
+ "fanri",
+ "fanta",
+ "fanva",
+ "fanza",
+ "fapro",
+ "farka",
+ "farlu",
+ "farna",
+ "farvi",
+ "fasnu",
+ "fatci",
+ "fatne",
+ "fatri",
+ "febvi",
+ "fegli",
+ "femti",
+ "fendi",
+ "fengu",
+ "fenki",
+ "fenra",
+ "fenso",
+ "fepni",
+ "fepri",
+ "ferti",
+ "festi",
+ "fetsi",
+ "figre",
+ "filso",
+ "finpe",
+ "finti",
+ "firca",
+ "fisli",
+ "fizbu",
+ "flaci",
+ "flalu",
+ "flani",
+ "flecu",
+ "flese",
+ "fliba",
+ "flira",
+ "foldi",
+ "fonmo",
+ "fonxa",
+ "forca",
+ "forse",
+ "fraso",
+ "frati",
+ "fraxu",
+ "frica",
+ "friko",
+ "frili",
+ "frinu",
+ "friti",
+ "frumu",
+ "fukpi",
+ "fulta",
+ "funca",
+ "fusra",
+ "fuzme",
+ "gacri",
+ "gadri",
+ "galfi",
+ "galtu",
+ "galxe",
+ "ganlo",
+ "ganra",
+ "ganse",
+ "ganti",
+ "ganxo",
+ "ganzu",
+ "gapci",
+ "gapru",
+ "garna",
+ "gasnu",
+ "gaspo",
+ "gasta",
+ "genja",
+ "gento",
+ "genxu",
+ "gerku",
+ "gerna",
+ "gidva",
+ "gigdo",
+ "ginka",
+ "girzu",
+ "gismu",
+ "glare",
+ "gleki",
+ "gletu",
+ "glico",
+ "glife",
+ "glosa",
+ "gluta",
+ "gocti",
+ "gomsi",
+ "gotro",
+ "gradu",
+ "grafu",
+ "grake",
+ "grana",
+ "grasu",
+ "grava",
+ "greku",
+ "grusi",
+ "grute",
+ "gubni",
+ "gugde",
+ "gugle",
+ "gumri",
+ "gundi",
+ "gunka",
+ "gunma",
+ "gunro",
+ "gunse",
+ "gunta",
+ "gurni",
+ "guska",
+ "gusni",
+ "gusta",
+ "gutci",
+ "gutra",
+ "guzme",
+ "jabre",
+ "jadni",
+ "jakne",
+ "jalge",
+ "jalna",
+ "jalra",
+ "jamfu",
+ "jamna",
+ "janbe",
+ "janco",
+ "janli",
+ "jansu",
+ "janta",
+ "jarbu",
+ "jarco",
+ "jarki",
+ "jaspu",
+ "jatna",
+ "javni",
+ "jbama",
+ "jbari",
+ "jbena",
+ "jbera",
+ "jbini",
+ "jdari",
+ "jdice",
+ "jdika",
+ "jdima",
+ "jdini",
+ "jduli",
+ "jecta",
+ "jeftu",
+ "jegvo",
+ "jelca",
+ "jemna",
+ "jenca",
+ "jendu",
+ "jenmi",
+ "jensi",
+ "jerna",
+ "jersi",
+ "jerxo",
+ "jesni",
+ "jetce",
+ "jetnu",
+ "jgalu",
+ "jganu",
+ "jgari",
+ "jgena",
+ "jgina",
+ "jgira",
+ "jgita",
+ "jibni",
+ "jibri",
+ "jicla",
+ "jicmu",
+ "jijnu",
+ "jikca",
+ "jikfi",
+ "jikni",
+ "jikru",
+ "jilka",
+ "jilra",
+ "jimca",
+ "jimpe",
+ "jimte",
+ "jinci",
+ "jinda",
+ "jinga",
+ "jinku",
+ "jinme",
+ "jinru",
+ "jinsa",
+ "jinto",
+ "jinvi",
+ "jinzi",
+ "jipci",
+ "jipno",
+ "jirna",
+ "jisra",
+ "jitfa",
+ "jitro",
+ "jivbu",
+ "jivna",
+ "jmaji",
+ "jmifa",
+ "jmina",
+ "jmive",
+ "jonse",
+ "jordo",
+ "jorne",
+ "jubme",
+ "judri",
+ "jufra",
+ "jukni",
+ "jukpa",
+ "julne",
+ "julro",
+ "jundi",
+ "jungo",
+ "junla",
+ "junri",
+ "junta",
+ "jurme",
+ "jursa",
+ "jutsi",
+ "juxre",
+ "jvinu",
+ "jviso",
+ "kabri",
+ "kacma",
+ "kadno",
+ "kafke",
+ "kagni",
+ "kajde",
+ "kajna",
+ "kakne",
+ "kakpa",
+ "kalci",
+ "kalri",
+ "kalsa",
+ "kalte",
+ "kamju",
+ "kamni",
+ "kampu",
+ "kamre",
+ "kanba",
+ "kancu",
+ "kandi",
+ "kanji",
+ "kanla",
+ "kanpe",
+ "kanro",
+ "kansa",
+ "kantu",
+ "kanxe",
+ "karbi",
+ "karce",
+ "karda",
+ "kargu",
+ "karli",
+ "karni",
+ "katci",
+ "katna",
+ "kavbu",
+ "kazra",
+ "kecti",
+ "kekli",
+ "kelci",
+ "kelvo",
+ "kenka",
+ "kenra",
+ "kensa",
+ "kerfa",
+ "kerlo",
+ "kesri",
+ "ketco",
+ "ketsu",
+ "kevna",
+ "kibro",
+ "kicne",
+ "kijno",
+ "kilto",
+ "kinda",
+ "kinli",
+ "kisto",
+ "klaji",
+ "klaku",
+ "klama",
+ "klani",
+ "klesi",
+ "kliki",
+ "klina",
+ "kliru",
+ "kliti",
+ "klupe",
+ "kluza",
+ "kobli",
+ "kogno",
+ "kojna",
+ "kokso",
+ "kolme",
+ "komcu",
+ "konju",
+ "korbi",
+ "korcu",
+ "korka",
+ "korvo",
+ "kosmu",
+ "kosta",
+ "krali",
+ "kramu",
+ "krasi",
+ "krati",
+ "krefu",
+ "krici",
+ "krili",
+ "krinu",
+ "krixa",
+ "kruca",
+ "kruji",
+ "kruvi",
+ "kubli",
+ "kucli",
+ "kufra",
+ "kukte",
+ "kulnu",
+ "kumfa",
+ "kumte",
+ "kunra",
+ "kunti",
+ "kurfa",
+ "kurji",
+ "kurki",
+ "kuspe",
+ "kusru",
+ "labno",
+ "lacni",
+ "lacpu",
+ "lacri",
+ "ladru",
+ "lafti",
+ "lakne",
+ "lakse",
+ "laldo",
+ "lalxu",
+ "lamji",
+ "lanbi",
+ "lanci",
+ "landa",
+ "lanka",
+ "lanli",
+ "lanme",
+ "lante",
+ "lanxe",
+ "lanzu",
+ "larcu",
+ "larva",
+ "lasna",
+ "lastu",
+ "latmo",
+ "latna",
+ "lazni",
+ "lebna",
+ "lelxe",
+ "lenga",
+ "lenjo",
+ "lenku",
+ "lerci",
+ "lerfu",
+ "libjo",
+ "lidne",
+ "lifri",
+ "lijda",
+ "limfa",
+ "limna",
+ "lince",
+ "lindi",
+ "linga",
+ "linji",
+ "linsi",
+ "linto",
+ "lisri",
+ "liste",
+ "litce",
+ "litki",
+ "litru",
+ "livga",
+ "livla",
+ "logji",
+ "loglo",
+ "lojbo",
+ "loldi",
+ "lorxu",
+ "lubno",
+ "lujvo",
+ "luksi",
+ "lumci",
+ "lunbe",
+ "lunra",
+ "lunsa",
+ "luska",
+ "lusto",
+ "mabla",
+ "mabru",
+ "macnu",
+ "majga",
+ "makcu",
+ "makfa",
+ "maksi",
+ "malsi",
+ "mamta",
+ "manci",
+ "manfo",
+ "mango",
+ "manku",
+ "manri",
+ "mansa",
+ "manti",
+ "mapku",
+ "mapni",
+ "mapra",
+ "mapti",
+ "marbi",
+ "marce",
+ "marde",
+ "margu",
+ "marji",
+ "marna",
+ "marxa",
+ "masno",
+ "masti",
+ "matci",
+ "matli",
+ "matne",
+ "matra",
+ "mavji",
+ "maxri",
+ "mebri",
+ "megdo",
+ "mekso",
+ "melbi",
+ "meljo",
+ "melmi",
+ "menli",
+ "menre",
+ "mensi",
+ "mentu",
+ "merko",
+ "merli",
+ "metfo",
+ "mexno",
+ "midju",
+ "mifra",
+ "mikce",
+ "mikri",
+ "milti",
+ "milxe",
+ "minde",
+ "minji",
+ "minli",
+ "minra",
+ "mintu",
+ "mipri",
+ "mirli",
+ "misno",
+ "misro",
+ "mitre",
+ "mixre",
+ "mlana",
+ "mlatu",
+ "mleca",
+ "mledi",
+ "mluni",
+ "mogle",
+ "mokca",
+ "moklu",
+ "molki",
+ "molro",
+ "morji",
+ "morko",
+ "morna",
+ "morsi",
+ "mosra",
+ "mraji",
+ "mrilu",
+ "mruli",
+ "mucti",
+ "mudri",
+ "mugle",
+ "mukti",
+ "mulno",
+ "munje",
+ "mupli",
+ "murse",
+ "murta",
+ "muslo",
+ "mutce",
+ "muvdu",
+ "muzga",
+ "nabmi",
+ "nakni",
+ "nalci",
+ "namcu",
+ "nanba",
+ "nanca",
+ "nandu",
+ "nanla",
+ "nanmu",
+ "nanvi",
+ "narge",
+ "narju",
+ "natfe",
+ "natmi",
+ "natsi",
+ "navni",
+ "naxle",
+ "nazbi",
+ "nejni",
+ "nelci",
+ "nenri",
+ "nerde",
+ "nibli",
+ "nicfa",
+ "nicte",
+ "nikle",
+ "nilce",
+ "nimre",
+ "ninja",
+ "ninmu",
+ "nirna",
+ "nitcu",
+ "nivji",
+ "nixli",
+ "nobli",
+ "norgo",
+ "notci",
+ "nudle",
+ "nukni",
+ "nunmu",
+ "nupre",
+ "nurma",
+ "nusna",
+ "nutka",
+ "nutli",
+ "nuzba",
+ "nuzlo",
+ "pacna",
+ "pagbu",
+ "pagre",
+ "pajni",
+ "palci",
+ "palku",
+ "palma",
+ "palne",
+ "palpi",
+ "palta",
+ "pambe",
+ "pamga",
+ "panci",
+ "pandi",
+ "panje",
+ "panka",
+ "panlo",
+ "panpi",
+ "panra",
+ "pante",
+ "panzi",
+ "papri",
+ "parbi",
+ "pardu",
+ "parji",
+ "pastu",
+ "patfu",
+ "patlu",
+ "patxu",
+ "paznu",
+ "pelji",
+ "pelxu",
+ "pemci",
+ "penbi",
+ "pencu",
+ "pendo",
+ "penmi",
+ "pensi",
+ "pentu",
+ "perli",
+ "pesxu",
+ "petso",
+ "pevna",
+ "pezli",
+ "picti",
+ "pijne",
+ "pikci",
+ "pikta",
+ "pilda",
+ "pilji",
+ "pilka",
+ "pilno",
+ "pimlu",
+ "pinca",
+ "pindi",
+ "pinfu",
+ "pinji",
+ "pinka",
+ "pinsi",
+ "pinta",
+ "pinxe",
+ "pipno",
+ "pixra",
+ "plana",
+ "platu",
+ "pleji",
+ "plibu",
+ "plini",
+ "plipe",
+ "plise",
+ "plita",
+ "plixa",
+ "pluja",
+ "pluka",
+ "pluta",
+ "pocli",
+ "polje",
+ "polno",
+ "ponjo",
+ "ponse",
+ "poplu",
+ "porpi",
+ "porsi",
+ "porto",
+ "prali",
+ "prami",
+ "prane",
+ "preja",
+ "prenu",
+ "preri",
+ "preti",
+ "prije",
+ "prina",
+ "pritu",
+ "proga",
+ "prosa",
+ "pruce",
+ "pruni",
+ "pruri",
+ "pruxi",
+ "pulce",
+ "pulji",
+ "pulni",
+ "punji",
+ "punli",
+ "pupsu",
+ "purci",
+ "purdi",
+ "purmo",
+ "racli",
+ "ractu",
+ "radno",
+ "rafsi",
+ "ragbi",
+ "ragve",
+ "rakle",
+ "rakso",
+ "raktu",
+ "ralci",
+ "ralju",
+ "ralte",
+ "randa",
+ "rango",
+ "ranji",
+ "ranmi",
+ "ransu",
+ "ranti",
+ "ranxi",
+ "rapli",
+ "rarna",
+ "ratcu",
+ "ratni",
+ "rebla",
+ "rectu",
+ "rekto",
+ "remna",
+ "renro",
+ "renvi",
+ "respa",
+ "rexsa",
+ "ricfu",
+ "rigni",
+ "rijno",
+ "rilti",
+ "rimni",
+ "rinci",
+ "rindo",
+ "rinju",
+ "rinka",
+ "rinsa",
+ "rirci",
+ "rirni",
+ "rirxe",
+ "rismi",
+ "risna",
+ "ritli",
+ "rivbi",
+ "rokci",
+ "romge",
+ "romlo",
+ "ronte",
+ "ropno",
+ "rorci",
+ "rotsu",
+ "rozgu",
+ "ruble",
+ "rufsu",
+ "runme",
+ "runta",
+ "rupnu",
+ "rusko",
+ "rutni",
+ "sabji",
+ "sabnu",
+ "sacki",
+ "saclu",
+ "sadjo",
+ "sakci",
+ "sakli",
+ "sakta",
+ "salci",
+ "salpo",
+ "salri",
+ "salta",
+ "samcu",
+ "sampu",
+ "sanbu",
+ "sance",
+ "sanga",
+ "sanji",
+ "sanli",
+ "sanmi",
+ "sanso",
+ "santa",
+ "sarcu",
+ "sarji",
+ "sarlu",
+ "sarni",
+ "sarxe",
+ "saske",
+ "satci",
+ "satre",
+ "savru",
+ "sazri",
+ "sefsi",
+ "sefta",
+ "sekre",
+ "selci",
+ "selfu",
+ "semto",
+ "senci",
+ "sengi",
+ "senpi",
+ "senta",
+ "senva",
+ "sepli",
+ "serti",
+ "sesre",
+ "setca",
+ "sevzi",
+ "sfani",
+ "sfasa",
+ "sfofa",
+ "sfubu",
+ "sibli",
+ "siclu",
+ "sicni",
+ "sicpi",
+ "sidbo",
+ "sidju",
+ "sigja",
+ "sigma",
+ "sikta",
+ "silka",
+ "silna",
+ "simlu",
+ "simsa",
+ "simxu",
+ "since",
+ "sinma",
+ "sinso",
+ "sinxa",
+ "sipna",
+ "sirji",
+ "sirxo",
+ "sisku",
+ "sisti",
+ "sitna",
+ "sivni",
+ "skaci",
+ "skami",
+ "skapi",
+ "skari",
+ "skicu",
+ "skiji",
+ "skina",
+ "skori",
+ "skoto",
+ "skuba",
+ "skuro",
+ "slabu",
+ "slaka",
+ "slami",
+ "slanu",
+ "slari",
+ "slasi",
+ "sligu",
+ "slilu",
+ "sliri",
+ "slovo",
+ "sluji",
+ "sluni",
+ "smacu",
+ "smadi",
+ "smaji",
+ "smaka",
+ "smani",
+ "smela",
+ "smoka",
+ "smuci",
+ "smuni",
+ "smusu",
+ "snada",
+ "snanu",
+ "snidu",
+ "snime",
+ "snipa",
+ "snuji",
+ "snura",
+ "snuti",
+ "sobde",
+ "sodna",
+ "sodva",
+ "softo",
+ "solji",
+ "solri",
+ "sombo",
+ "sonci",
+ "sorcu",
+ "sorgu",
+ "sorni",
+ "sorta",
+ "sovda",
+ "spaji",
+ "spali",
+ "spano",
+ "spati",
+ "speni",
+ "spero",
+ "spisa",
+ "spita",
+ "spofu",
+ "spoja",
+ "spuda",
+ "sputu",
+ "sraji",
+ "sraku",
+ "sralo",
+ "srana",
+ "srasu",
+ "srera",
+ "srito",
+ "sruma",
+ "sruri",
+ "stace",
+ "stagi",
+ "staku",
+ "stali",
+ "stani",
+ "stapa",
+ "stasu",
+ "stati",
+ "steba",
+ "steci",
+ "stedu",
+ "stela",
+ "stero",
+ "stici",
+ "stidi",
+ "stika",
+ "stizu",
+ "stodi",
+ "stuna",
+ "stura",
+ "stuzi",
+ "sucta",
+ "sudga",
+ "sufti",
+ "suksa",
+ "sumji",
+ "sumne",
+ "sumti",
+ "sunga",
+ "sunla",
+ "surla",
+ "sutra",
+ "tabno",
+ "tabra",
+ "tadji",
+ "tadni",
+ "tagji",
+ "taksi",
+ "talsa",
+ "tamca",
+ "tamji",
+ "tamne",
+ "tanbo",
+ "tance",
+ "tanjo",
+ "tanko",
+ "tanru",
+ "tansi",
+ "tanxe",
+ "tapla",
+ "tarbi",
+ "tarci",
+ "tarla",
+ "tarmi",
+ "tarti",
+ "taske",
+ "tasmi",
+ "tasta",
+ "tatpi",
+ "tatru",
+ "tavla",
+ "taxfu",
+ "tcaci",
+ "tcadu",
+ "tcana",
+ "tcati",
+ "tcaxe",
+ "tcena",
+ "tcese",
+ "tcica",
+ "tcidu",
+ "tcika",
+ "tcila",
+ "tcima",
+ "tcini",
+ "tcita",
+ "temci",
+ "temse",
+ "tende",
+ "tenfa",
+ "tengu",
+ "terdi",
+ "terpa",
+ "terto",
+ "tifri",
+ "tigni",
+ "tigra",
+ "tikpa",
+ "tilju",
+ "tinbe",
+ "tinci",
+ "tinsa",
+ "tirna",
+ "tirse",
+ "tirxu",
+ "tisna",
+ "titla",
+ "tivni",
+ "tixnu",
+ "toknu",
+ "toldi",
+ "tonga",
+ "tordu",
+ "torni",
+ "torso",
+ "traji",
+ "trano",
+ "trati",
+ "trene",
+ "tricu",
+ "trina",
+ "trixe",
+ "troci",
+ "tsaba",
+ "tsali",
+ "tsani",
+ "tsapi",
+ "tsiju",
+ "tsina",
+ "tsuku",
+ "tubnu",
+ "tubra",
+ "tugni",
+ "tujli",
+ "tumla",
+ "tunba",
+ "tunka",
+ "tunlo",
+ "tunta",
+ "tuple",
+ "turko",
+ "turni",
+ "tutci",
+ "tutle",
+ "tutra",
+ "vacri",
+ "vajni",
+ "valsi",
+ "vamji",
+ "vamtu",
+ "vanbi",
+ "vanci",
+ "vanju",
+ "vasru",
+ "vasxu",
+ "vecnu",
+ "vedli",
+ "venfu",
+ "vensa",
+ "vente",
+ "vepre",
+ "verba",
+ "vibna",
+ "vidni",
+ "vidru",
+ "vifne",
+ "vikmi",
+ "viknu",
+ "vimcu",
+ "vindu",
+ "vinji",
+ "vinta",
+ "vipsi",
+ "virnu",
+ "viska",
+ "vitci",
+ "vitke",
+ "vitno",
+ "vlagi",
+ "vlile",
+ "vlina",
+ "vlipa",
+ "vofli",
+ "voksa",
+ "volve",
+ "vorme",
+ "vraga",
+ "vreji",
+ "vreta",
+ "vrici",
+ "vrude",
+ "vrusi",
+ "vubla",
+ "vujnu",
+ "vukna",
+ "vukro",
+ "xabju",
+ "xadba",
+ "xadji",
+ "xadni",
+ "xagji",
+ "xagri",
+ "xajmi",
+ "xaksu",
+ "xalbo",
+ "xalka",
+ "xalni",
+ "xamgu",
+ "xampo",
+ "xamsi",
+ "xance",
+ "xango",
+ "xanka",
+ "xanri",
+ "xansa",
+ "xanto",
+ "xarci",
+ "xarju",
+ "xarnu",
+ "xasli",
+ "xasne",
+ "xatra",
+ "xatsi",
+ "xazdo",
+ "xebni",
+ "xebro",
+ "xecto",
+ "xedja",
+ "xekri",
+ "xelso",
+ "xendo",
+ "xenru",
+ "xexso",
+ "xigzo",
+ "xindo",
+ "xinmo",
+ "xirma",
+ "xislu",
+ "xispo",
+ "xlali",
+ "xlura",
+ "xorbo",
+ "xorlo",
+ "xotli",
+ "xrabo",
+ "xrani",
+ "xriso",
+ "xrotu",
+ "xruba",
+ "xruki",
+ "xrula",
+ "xruti",
+ "xukmi",
+ "xulta",
+ "xunre",
+ "xurdo",
+ "xusra",
+ "xutla",
+ "zabna",
+ "zajba",
+ "zalvi",
+ "zanru",
+ "zarci",
+ "zargu",
+ "zasni",
+ "zasti",
+ "zbabu",
+ "zbani",
+ "zbasu",
+ "zbepi",
+ "zdani",
+ "zdile",
+ "zekri",
+ "zenba",
+ "zepti",
+ "zetro",
+ "zevla",
+ "zgadi",
+ "zgana",
+ "zgike",
+ "zifre",
+ "zinki",
+ "zirpu",
+ "zivle",
+ "zmadu",
+ "zmiku",
+ "zucna",
+ "zukte",
+ "zumri",
+ "zungi",
+ "zunle",
+ "zunti",
+ "zutse",
+ "zvati",
+ "zviki",
+ "jbobau",
+ "jbopre",
+ "karsna",
+ "cabdei",
+ "zunsna",
+ "gendra",
+ "glibau",
+ "nintadni",
+ "pavyseljirna",
+ "vlaste",
+ "selbri",
+ "latro'a",
+ "zdakemkulgu'a",
+ "mriste",
+ "selsku",
+ "fu'ivla",
+ "tolmo'i",
+ "snavei",
+ "xagmau",
+ "retsku",
+ "ckupau",
+ "skudji",
+ "smudra",
+ "prulamdei",
+ "vokta'a",
+ "tinju'i",
+ "jefyfa'o",
+ "bavlamdei",
+ "kinzga",
+ "jbocre",
+ "jbovla",
+ "xauzma",
+ "selkei",
+ "xuncku",
+ "spusku",
+ "jbogu'e",
+ "pampe'o",
+ "bripre",
+ "jbosnu",
+ "zi'evla",
+ "gimste",
+ "tolzdi",
+ "velski",
+ "samselpla",
+ "cnegau",
+ "velcki",
+ "selja'e",
+ "fasybau",
+ "zanfri",
+ "reisku",
+ "favgau",
+ "jbota'a",
+ "rejgau",
+ "malgli",
+ "zilkai",
+ "keidji",
+ "tersu'i",
+ "jbofi'e",
+ "cnima'o",
+ "mulgau",
+ "ningau",
+ "ponbau",
+ "mrobi'o",
+ "rarbau",
+ "zmanei",
+ "famyma'o",
+ "vacysai",
+ "jetmlu",
+ "jbonunsla",
+ "nunpe'i",
+ "fa'orma'o",
+ "crezenzu'e",
+ "jbojbe",
+ "cmicu'a",
+ "zilcmi",
+ "tolcando",
+ "zukcfu",
+ "depybu'i",
+ "mencre",
+ "matmau",
+ "nunctu",
+ "selma'o",
+ "titnanba",
+ "naldra",
+ "jvajvo",
+ "nunsnu",
+ "nerkla",
+ "cimjvo",
+ "muvgau",
+ "zipcpi",
+ "runbau",
+ "faumlu",
+ "terbri",
+ "balcu'e",
+ "dragau",
+ "smuvelcki",
+ "piksku",
+ "selpli",
+ "bregau",
+ "zvafa'i",
+ "ci'izra",
+ "noltruti'u",
+ "samtci",
+ "snaxa'a",
+ }), 4)
+ {
+ populate_maps();
+ }
+ };
+}
+
+#endif
diff --git a/src/p2p/CMakeLists.txt b/src/p2p/CMakeLists.txt
index 0704940b5..123b0a272 100644
--- a/src/p2p/CMakeLists.txt
+++ b/src/p2p/CMakeLists.txt
@@ -39,6 +39,7 @@ monero_add_library(p2p ${P2P})
target_link_libraries(p2p
PUBLIC
epee
+ version
${UPNP_LIBRARIES}
${Boost_CHRONO_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY}
@@ -47,5 +48,3 @@ target_link_libraries(p2p
${Boost_THREAD_LIBRARY}
PRIVATE
${EXTRA_LIBRARIES})
-add_dependencies(p2p
- version)
diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h
index 22ae5ec26..8bbaa9138 100644
--- a/src/p2p/net_node.h
+++ b/src/p2p/net_node.h
@@ -105,7 +105,7 @@ namespace nodetool
bool init(const boost::program_options::variables_map& vm);
bool deinit();
bool send_stop_signal();
- uint32_t get_this_peer_port(){return m_listenning_port;}
+ uint32_t get_this_peer_port(){return m_listening_port;}
t_payload_net_handler& get_payload_object();
template <class Archive, class t_version_type>
@@ -218,6 +218,8 @@ namespace nodetool
bool is_peer_used(const peerlist_entry& peer);
bool is_peer_used(const anchor_peerlist_entry& peer);
bool is_addr_connected(const epee::net_utils::network_address& peer);
+ void add_upnp_port_mapping(uint32_t port);
+ void delete_upnp_port_mapping(uint32_t port);
template<class t_callback>
bool try_ping(basic_node_data& node_data, p2p_connection_context& context, t_callback cb);
bool try_get_support_flags(const p2p_connection_context& context, std::function<void(p2p_connection_context&, const uint32_t&)> f);
@@ -287,7 +289,7 @@ namespace nodetool
bool m_have_address;
bool m_first_connection_maker_call;
- uint32_t m_listenning_port;
+ uint32_t m_listening_port;
uint32_t m_external_port;
uint32_t m_ip_address;
bool m_allow_local_ip;
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 889cfaf9d..6162d649b 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -390,7 +390,7 @@ namespace nodetool
ip::tcp::endpoint endpoint = *i;
if (endpoint.address().is_v4())
{
- epee::net_utils::network_address na(new epee::net_utils::ipv4_network_address(boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()), endpoint.port()));
+ epee::net_utils::network_address na{epee::net_utils::ipv4_network_address{boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()), endpoint.port()}};
seed_nodes.push_back(na);
MINFO("Added seed node: " << na.str());
}
@@ -573,55 +573,15 @@ namespace nodetool
res = m_net_server.init_server(m_port, m_bind_ip);
CHECK_AND_ASSERT_MES(res, false, "Failed to bind server");
- m_listenning_port = m_net_server.get_binded_port();
- MLOG_GREEN(el::Level::Info, "Net service bound to " << m_bind_ip << ":" << m_listenning_port);
+ m_listening_port = m_net_server.get_binded_port();
+ MLOG_GREEN(el::Level::Info, "Net service bound to " << m_bind_ip << ":" << m_listening_port);
if(m_external_port)
MDEBUG("External port defined as " << m_external_port);
- // Add UPnP port mapping
- if(m_no_igd == false) {
- MDEBUG("Attempting to add IGD port mapping.");
- int result;
-#if MINIUPNPC_API_VERSION > 13
- // default according to miniupnpc.h
- unsigned char ttl = 2;
- UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result);
-#else
- UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result);
-#endif
- UPNPUrls urls;
- IGDdatas igdData;
- char lanAddress[64];
- result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress);
- freeUPNPDevlist(deviceList);
- if (result != 0) {
- if (result == 1) {
- std::ostringstream portString;
- portString << m_listenning_port;
-
- // Delete the port mapping before we create it, just in case we have dangling port mapping from the daemon not being shut down correctly
- UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0);
-
- int portMappingResult;
- portMappingResult = UPNP_AddPortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), portString.str().c_str(), lanAddress, CRYPTONOTE_NAME, "TCP", 0, "0");
- if (portMappingResult != 0) {
- LOG_ERROR("UPNP_AddPortMapping failed, error: " << strupnperror(portMappingResult));
- } else {
- MLOG_GREEN(el::Level::Info, "Added IGD port mapping.");
- }
- } else if (result == 2) {
- MWARNING("IGD was found but reported as not connected.");
- } else if (result == 3) {
- MWARNING("UPnP device was found but not recognized as IGD.");
- } else {
- MWARNING("UPNP_GetValidIGD returned an unknown result code.");
- }
+ // add UPnP port mapping
+ if(!m_no_igd)
+ add_upnp_port_mapping(m_listening_port);
- FreeUPNPUrls(&urls);
- } else {
- MINFO("No IGD was found.");
- }
- }
return res;
}
//-----------------------------------------------------------------------------------
@@ -688,6 +648,9 @@ namespace nodetool
kill();
m_peerlist.deinit();
m_net_server.deinit_server();
+ // remove UPnP port mapping
+ if(!m_no_igd)
+ delete_upnp_port_mapping(m_listening_port);
return store_config();
}
//-----------------------------------------------------------------------------------
@@ -1127,6 +1090,8 @@ namespace nodetool
if (use_white_list) {
local_peers_count = m_peerlist.get_white_peers_count();
+ if (!local_peers_count)
+ return false;
max_random_index = std::min<uint64_t>(local_peers_count -1, 20);
random_index = get_random_index_with_fixed_probability(max_random_index);
} else {
@@ -1389,7 +1354,7 @@ namespace nodetool
node_data.local_time = local_time;
node_data.peer_id = m_config.m_peer_id;
if(!m_hide_my_port)
- node_data.my_port = m_external_port ? m_external_port : m_listenning_port;
+ node_data.my_port = m_external_port ? m_external_port : m_listening_port;
else
node_data.my_port = 0;
node_data.network_id = m_network_id;
@@ -1556,7 +1521,7 @@ namespace nodetool
return false;
std::string ip = epee::string_tools::get_ip_string_from_int32(actual_ip);
std::string port = epee::string_tools::num_to_string_fast(node_data.my_port);
- epee::net_utils::network_address address(new epee::net_utils::ipv4_network_address(actual_ip, node_data.my_port));
+ epee::net_utils::network_address address{epee::net_utils::ipv4_network_address(actual_ip, node_data.my_port)};
peerid_type pr = node_data.peer_id;
bool r = m_net_server.connect_async(ip, port, m_config.m_net_config.ping_connection_timeout, [cb, /*context,*/ address, pr, this](
const typename net_server::t_connection_context& ping_context,
@@ -1704,7 +1669,7 @@ namespace nodetool
if(arg.node_data.peer_id != m_config.m_peer_id && arg.node_data.my_port)
{
peerid_type peer_id_l = arg.node_data.peer_id;
- uint32_t port_l = arg.node_data.my_port;
+ uint32_t port_l = arg.node_data.my_port;
//try ping to be sure that we can add this peer to peer_list
try_ping(arg.node_data, context, [peer_id_l, port_l, context, this]()
{
@@ -1713,7 +1678,7 @@ namespace nodetool
//called only(!) if success pinged, update local peerlist
peerlist_entry pe;
const epee::net_utils::network_address na = context.m_remote_address;
- pe.adr.reset(new epee::net_utils::ipv4_network_address(na.as<epee::net_utils::ipv4_network_address>().ip(), port_l));
+ pe.adr = epee::net_utils::ipv4_network_address(na.as<epee::net_utils::ipv4_network_address>().ip(), port_l);
time_t last_seen;
time(&last_seen);
pe.last_seen = static_cast<int64_t>(last_seen);
@@ -1951,8 +1916,13 @@ namespace nodetool
template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::gray_peerlist_housekeeping()
{
+ if (!m_exclusive_peers.empty()) return true;
+
peerlist_entry pe = AUTO_VAL_INIT(pe);
+ if (m_net_server.is_stop_signal_sent())
+ return false;
+
if (!m_peerlist.get_random_gray_peer(pe)) {
return false;
}
@@ -1973,4 +1943,93 @@ namespace nodetool
return true;
}
+
+ template<class t_payload_net_handler>
+ void node_server<t_payload_net_handler>::add_upnp_port_mapping(uint32_t port)
+ {
+ MDEBUG("Attempting to add IGD port mapping.");
+ int result;
+#if MINIUPNPC_API_VERSION > 13
+ // default according to miniupnpc.h
+ unsigned char ttl = 2;
+ UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result);
+#else
+ UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result);
+#endif
+ UPNPUrls urls;
+ IGDdatas igdData;
+ char lanAddress[64];
+ result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress);
+ freeUPNPDevlist(deviceList);
+ if (result != 0) {
+ if (result == 1) {
+ std::ostringstream portString;
+ portString << port;
+
+ // Delete the port mapping before we create it, just in case we have dangling port mapping from the daemon not being shut down correctly
+ UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0);
+
+ int portMappingResult;
+ portMappingResult = UPNP_AddPortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), portString.str().c_str(), lanAddress, CRYPTONOTE_NAME, "TCP", 0, "0");
+ if (portMappingResult != 0) {
+ LOG_ERROR("UPNP_AddPortMapping failed, error: " << strupnperror(portMappingResult));
+ } else {
+ MLOG_GREEN(el::Level::Info, "Added IGD port mapping.");
+ }
+ } else if (result == 2) {
+ MWARNING("IGD was found but reported as not connected.");
+ } else if (result == 3) {
+ MWARNING("UPnP device was found but not recognized as IGD.");
+ } else {
+ MWARNING("UPNP_GetValidIGD returned an unknown result code.");
+ }
+
+ FreeUPNPUrls(&urls);
+ } else {
+ MINFO("No IGD was found.");
+ }
+ }
+
+ template<class t_payload_net_handler>
+ void node_server<t_payload_net_handler>::delete_upnp_port_mapping(uint32_t port)
+ {
+ MDEBUG("Attempting to delete IGD port mapping.");
+ int result;
+#if MINIUPNPC_API_VERSION > 13
+ // default according to miniupnpc.h
+ unsigned char ttl = 2;
+ UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result);
+#else
+ UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result);
+#endif
+ UPNPUrls urls;
+ IGDdatas igdData;
+ char lanAddress[64];
+ result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress);
+ freeUPNPDevlist(deviceList);
+ if (result != 0) {
+ if (result == 1) {
+ std::ostringstream portString;
+ portString << port;
+
+ int portMappingResult;
+ portMappingResult = UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0);
+ if (portMappingResult != 0) {
+ LOG_ERROR("UPNP_DeletePortMapping failed, error: " << strupnperror(portMappingResult));
+ } else {
+ MLOG_GREEN(el::Level::Info, "Deleted IGD port mapping.");
+ }
+ } else if (result == 2) {
+ MWARNING("IGD was found but reported as not connected.");
+ } else if (result == 3) {
+ MWARNING("UPnP device was found but not recognized as IGD.");
+ } else {
+ MWARNING("UPNP_GetValidIGD returned an unknown result code.");
+ }
+
+ FreeUPNPUrls(&urls);
+ } else {
+ MINFO("No IGD was found.");
+ }
+ }
}
diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h
index a30a05422..8372445aa 100644
--- a/src/p2p/net_peerlist.h
+++ b/src/p2p/net_peerlist.h
@@ -190,35 +190,16 @@ namespace nodetool
if (ver < 6)
return;
- if(ver < 3)
- return;
CRITICAL_REGION_LOCAL(m_peerlist_lock);
- if(ver < 4)
- {
- //loading data from old storage
- peers_indexed_old pio;
- a & pio;
- peers_indexed_from_old(pio, m_peers_white);
- return;
- }
#if 0
// trouble loading more than one peer, can't find why
a & m_peers_white;
a & m_peers_gray;
+ a & m_peers_anchor;
#else
serialize_peers(a, m_peers_white, peerlist_entry(), ver);
serialize_peers(a, m_peers_gray, peerlist_entry(), ver);
-#endif
-
- if(ver < 5) {
- return;
- }
-
-#if 0
- // trouble loading more than one peer, can't find why
- a & m_peers_anchor;
-#else
serialize_peers(a, m_peers_anchor, anchor_peerlist_entry(), ver);
#endif
}
diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h
index 6ea2d48fd..079524aa1 100644
--- a/src/p2p/net_peerlist_boost_serialization.h
+++ b/src/p2p/net_peerlist_boost_serialization.h
@@ -31,11 +31,19 @@
#pragma once
#include "net/net_utils_base.h"
+#include "p2p/p2p_protocol_defs.h"
namespace boost
{
namespace serialization
{
+ template <class T, class Archive>
+ inline void do_serialize(Archive &a, epee::net_utils::network_address& na, T local)
+ {
+ if (typename Archive::is_saving()) local = na.as<T>();
+ a & local;
+ if (!typename Archive::is_saving()) na = local;
+ }
template <class Archive, class ver_type>
inline void serialize(Archive &a, epee::net_utils::network_address& na, const ver_type ver)
{
@@ -46,10 +54,8 @@ namespace boost
switch (type)
{
case epee::net_utils::ipv4_network_address::ID:
- if (!typename Archive::is_saving())
- na.reset(new epee::net_utils::ipv4_network_address(0, 0));
- a & na.as<epee::net_utils::ipv4_network_address>();
- break;
+ do_serialize(a, na, epee::net_utils::ipv4_network_address{0, 0});
+ break;
default:
throw std::runtime_error("Unsupported network address type");
}
@@ -57,13 +63,14 @@ namespace boost
template <class Archive, class ver_type>
inline void serialize(Archive &a, epee::net_utils::ipv4_network_address& na, const ver_type ver)
{
- a & na.m_ip;
- a & na.m_port;
+ uint32_t ip{na.ip()};
+ uint16_t port{na.port()};
+ a & ip;
+ a & port;
if (!typename Archive::is_saving())
- na.init_ids();
+ na = epee::net_utils::ipv4_network_address{ip, port};
}
-
template <class Archive, class ver_type>
inline void serialize(Archive &a, nodetool::peerlist_entry& pl, const ver_type ver)
{
diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h
index f2b2cd1da..d49d83989 100644
--- a/src/p2p/p2p_protocol_defs.h
+++ b/src/p2p/p2p_protocol_defs.h
@@ -120,7 +120,7 @@ namespace nodetool
ss << std::setfill ('0') << std::setw (8) << std::hex << std::noshowbase;
for(const peerlist_entry& pe: pl)
{
- ss << pe.id << "\t" << pe.adr->str() << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) << std::endl;
+ ss << pe.id << "\t" << pe.adr.str() << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) << std::endl;
}
return ss.str();
}
@@ -216,7 +216,7 @@ namespace nodetool
std::list<peerlist_entry_base<network_address_old>> local_peerlist;
epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist");
for (const auto &p: local_peerlist)
- ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({new epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen}));
+ ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen}));
}
}
END_KV_SERIALIZE_MAP()
@@ -275,7 +275,7 @@ namespace nodetool
std::list<peerlist_entry_base<network_address_old>> local_peerlist;
epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist");
for (const auto &p: local_peerlist)
- ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({new epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen}));
+ ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen}));
}
}
END_KV_SERIALIZE_MAP()
diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h
index c820fb297..cc0000ad6 100644
--- a/src/ringct/rctTypes.h
+++ b/src/ringct/rctTypes.h
@@ -445,9 +445,11 @@ namespace cryptonote {
static inline bool operator!=(const crypto::secret_key &k0, const rct::key &k1) { return memcmp(&k0, &k1, 32); }
}
+namespace rct {
inline std::ostream &operator <<(std::ostream &o, const rct::key &v) {
epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
}
+}
BLOB_SERIALIZER(rct::key);
diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt
index b5c38b1a8..23bb6aaae 100644
--- a/src/rpc/CMakeLists.txt
+++ b/src/rpc/CMakeLists.txt
@@ -117,10 +117,3 @@ target_link_libraries(daemon_rpc_server
${EXTRA_LIBRARIES})
target_include_directories(daemon_rpc_server PUBLIC ${ZMQ_INCLUDE_PATH})
target_include_directories(obj_daemon_rpc_server PUBLIC ${ZMQ_INCLUDE_PATH})
-
-
-add_dependencies(rpc
- version)
-
-add_dependencies(daemon_rpc_server
- version)
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 1f7f4a1ff..b3ce30d0c 100644..100755
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -50,6 +50,16 @@ using namespace epee;
#define MAX_RESTRICTED_FAKE_OUTS_COUNT 40
#define MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT 500
+namespace
+{
+ void add_reason(std::string &reasons, const char *reason)
+ {
+ if (!reasons.empty())
+ reasons += ", ";
+ reasons += reason;
+ }
+}
+
namespace cryptonote
{
@@ -91,7 +101,7 @@ namespace cryptonote
http_login.emplace(std::move(rpc_config->login->username), std::move(rpc_config->login->password).password());
return epee::http_server_impl_base<core_rpc_server, connection_context>::init(
- std::move(port), std::move(rpc_config->bind_ip), std::move(http_login)
+ std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login)
);
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -128,11 +138,7 @@ namespace cryptonote
{
CHECK_CORE_BUSY();
crypto::hash top_hash;
- if (!m_core.get_blockchain_top(res.height, top_hash))
- {
- res.status = "Failed";
- return false;
- }
+ m_core.get_blockchain_top(res.height, top_hash);
++res.height; // turn top block height into blockchain height
res.top_block_hash = string_tools::pod_to_hex(top_hash);
res.target_height = m_core.get_target_blockchain_height();
@@ -478,18 +484,31 @@ namespace cryptonote
bool r = m_core.get_pool_transactions(pool_txs);
if(r)
{
- for (std::list<transaction>::const_iterator i = pool_txs.begin(); i != pool_txs.end(); ++i)
+ // sort to match original request
+ std::list<transaction> sorted_txs;
+ std::list<cryptonote::transaction>::const_iterator i;
+ for (const crypto::hash &h: vh)
{
- crypto::hash tx_hash = get_transaction_hash(*i);
- std::list<crypto::hash>::iterator mi = std::find(missed_txs.begin(), missed_txs.end(), tx_hash);
- if (mi != missed_txs.end())
+ if (std::find(missed_txs.begin(), missed_txs.end(), h) == missed_txs.end())
{
- pool_tx_hashes.insert(tx_hash);
- missed_txs.erase(mi);
- txs.push_back(*i);
+ // core returns the ones it finds in the right order
+ if (get_transaction_hash(txs.front()) != h)
+ {
+ res.status = "Failed: tx hash mismatch";
+ return true;
+ }
+ sorted_txs.push_back(std::move(txs.front()));
+ txs.pop_front();
+ }
+ else if ((i = std::find_if(pool_txs.begin(), pool_txs.end(), [h](cryptonote::transaction &tx) { return h == cryptonote::get_transaction_hash(tx); })) != pool_txs.end())
+ {
+ sorted_txs.push_back(*i);
+ missed_txs.remove(h);
+ pool_tx_hashes.insert(h);
++found_in_pool;
}
}
+ txs = sorted_txs;
}
LOG_PRINT_L2("Found " << found_in_pool << "/" << vh.size() << " transactions in the pool");
}
@@ -510,11 +529,12 @@ namespace cryptonote
e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end();
if (e.in_pool)
{
- e.block_height = std::numeric_limits<uint64_t>::max();
+ e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max();
}
else
{
e.block_height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash);
+ e.block_timestamp = m_core.get_blockchain_storage().get_db().get_block_timestamp(e.block_height);
}
// fill up old style responses too, in case an old wallet asks
@@ -623,31 +643,33 @@ namespace cryptonote
tx_verification_context tvc = AUTO_VAL_INIT(tvc);
if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false, req.do_not_relay) || tvc.m_verifivation_failed)
{
- if (tvc.m_verifivation_failed)
- {
- LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed");
- }
- else
- {
- LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx");
- }
res.status = "Failed";
+ res.reason = "";
if ((res.low_mixin = tvc.m_low_mixin))
- res.reason = "ring size too small";
+ add_reason(res.reason, "ring size too small");
if ((res.double_spend = tvc.m_double_spend))
- res.reason = "double spend";
+ add_reason(res.reason, "double spend");
if ((res.invalid_input = tvc.m_invalid_input))
- res.reason = "invalid input";
+ add_reason(res.reason, "invalid input");
if ((res.invalid_output = tvc.m_invalid_output))
- res.reason = "invalid output";
+ add_reason(res.reason, "invalid output");
if ((res.too_big = tvc.m_too_big))
- res.reason = "too big";
+ add_reason(res.reason, "too big");
if ((res.overspend = tvc.m_overspend))
- res.reason = "overspend";
+ add_reason(res.reason, "overspend");
if ((res.fee_too_low = tvc.m_fee_too_low))
- res.reason = "fee too low";
+ add_reason(res.reason, "fee too low");
if ((res.not_rct = tvc.m_not_rct))
- res.reason = "tx is not ringct";
+ add_reason(res.reason, "tx is not ringct");
+ const std::string punctuation = res.reason.empty() ? "" : ": ";
+ if (tvc.m_verifivation_failed)
+ {
+ LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed" << punctuation << res.reason);
+ }
+ else
+ {
+ LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx" << punctuation << res.reason);
+ }
return true;
}
@@ -671,13 +693,19 @@ namespace cryptonote
bool core_rpc_server::on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res)
{
CHECK_CORE_READY();
- account_public_address adr;
- if(!get_account_address_from_str(adr, m_testnet, req.miner_address))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, m_testnet, req.miner_address))
{
res.status = "Failed, wrong address";
LOG_PRINT_L0(res.status);
return true;
}
+ if (info.is_subaddress)
+ {
+ res.status = "Mining to subaddress isn't supported yet";
+ LOG_PRINT_L0(res.status);
+ return true;
+ }
unsigned int concurrency_count = boost::thread::hardware_concurrency() * 4;
@@ -699,7 +727,7 @@ namespace cryptonote
boost::thread::attributes attrs;
attrs.set_stack_size(THREAD_STACK_SIZE);
- if(!m_core.get_miner().start(adr, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery))
+ if(!m_core.get_miner().start(info.address, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery))
{
res.status = "Failed, mining not started";
LOG_PRINT_L0(res.status);
@@ -733,7 +761,7 @@ namespace cryptonote
res.speed = lMiner.get_speed();
res.threads_count = lMiner.get_threads_count();
const account_public_address& lMiningAdr = lMiner.get_mining_address();
- res.address = get_account_address_as_str(m_testnet, lMiningAdr);
+ res.address = get_account_address_as_str(m_testnet, false, lMiningAdr);
}
res.status = CORE_RPC_STATUS_OK;
@@ -810,6 +838,7 @@ namespace cryptonote
bool core_rpc_server::on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res)
{
mlog_set_log(req.categories.c_str());
+ res.categories = mlog_get_categories();
res.status = CORE_RPC_STATUS_OK;
return true;
}
@@ -912,19 +941,25 @@ namespace cryptonote
return false;
}
- cryptonote::account_public_address acc = AUTO_VAL_INIT(acc);
+ cryptonote::address_parse_info info;
- if(!req.wallet_address.size() || !cryptonote::get_account_address_from_str(acc, m_testnet, req.wallet_address))
+ if(!req.wallet_address.size() || !cryptonote::get_account_address_from_str(info, m_testnet, req.wallet_address))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_WALLET_ADDRESS;
error_resp.message = "Failed to parse wallet address";
return false;
}
+ if (info.is_subaddress)
+ {
+ error_resp.code = CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS;
+ error_resp.message = "Mining to subaddress is not supported yet";
+ return false;
+ }
block b = AUTO_VAL_INIT(b);
cryptonote::blobdata blob_reserve;
blob_reserve.resize(req.reserve_size, 0);
- if(!m_core.get_block_template(b, acc, res.difficulty, res.height, res.expected_reward, blob_reserve))
+ if(!m_core.get_block_template(b, info.address, res.difficulty, res.height, res.expected_reward, blob_reserve))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: failed to create block template";
@@ -948,7 +983,7 @@ namespace cryptonote
LOG_ERROR("Failed to find tx pub key in blockblob");
return false;
}
- res.reserved_offset += sizeof(tx_pub_key) + 3; //3 bytes: tag for TX_EXTRA_TAG_PUBKEY(1 byte), tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte)
+ res.reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte)
if(res.reserved_offset + req.reserve_size > block_blob.size())
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
@@ -1048,13 +1083,7 @@ namespace cryptonote
}
uint64_t last_block_height;
crypto::hash last_block_hash;
- bool have_last_block_hash = m_core.get_blockchain_top(last_block_height, last_block_hash);
- if (!have_last_block_hash)
- {
- error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
- error_resp.message = "Internal error: can't get last block hash.";
- return false;
- }
+ m_core.get_blockchain_top(last_block_height, last_block_hash);
block last_block;
bool have_last_block = m_core.get_block_by_hash(last_block_hash, last_block);
if (!have_last_block)
@@ -1287,11 +1316,7 @@ namespace cryptonote
}
crypto::hash top_hash;
- if (!m_core.get_blockchain_top(res.height, top_hash))
- {
- res.status = "Failed";
- return false;
- }
+ m_core.get_blockchain_top(res.height, top_hash);
++res.height; // turn top block height into blockchain height
res.top_block_hash = string_tools::pod_to_hex(top_hash);
res.target_height = m_core.get_target_blockchain_height();
@@ -1383,7 +1408,7 @@ namespace cryptonote
}
else
{
- na.reset(new epee::net_utils::ipv4_network_address(i->ip, 0));
+ na = epee::net_utils::ipv4_network_address{i->ip, 0};
}
if (i->ban)
m_p2p.block_host(na, i->seconds);
@@ -1522,23 +1547,62 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool core_rpc_server::on_get_limit(const COMMAND_RPC_GET_LIMIT::request& req, COMMAND_RPC_GET_LIMIT::response& res)
+ {
+ res.limit_down = epee::net_utils::connection_basic::get_rate_down_limit();
+ res.limit_up = epee::net_utils::connection_basic::get_rate_up_limit();
+ res.status = CORE_RPC_STATUS_OK;
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool core_rpc_server::on_set_limit(const COMMAND_RPC_SET_LIMIT::request& req, COMMAND_RPC_SET_LIMIT::response& res)
+ {
+ // -1 = reset to default
+ // 0 = do not modify
+
+ if (req.limit_down > 0)
+ {
+ epee::net_utils::connection_basic::set_rate_down_limit(req.limit_down);
+ }
+ else if (req.limit_down < 0)
+ {
+ if (req.limit_down != -1)
+ {
+ res.status = CORE_RPC_ERROR_CODE_WRONG_PARAM;
+ return false;
+ }
+ epee::net_utils::connection_basic::set_rate_down_limit(nodetool::default_limit_down * 1024);
+ }
+
+ if (req.limit_up > 0)
+ {
+ epee::net_utils::connection_basic::set_rate_up_limit(req.limit_up);
+ }
+ else if (req.limit_up < 0)
+ {
+ if (req.limit_up != -1)
+ {
+ res.status = CORE_RPC_ERROR_CODE_WRONG_PARAM;
+ return false;
+ }
+ epee::net_utils::connection_basic::set_rate_up_limit(nodetool::default_limit_up * 1024);
+ }
+
+ res.limit_down = epee::net_utils::connection_basic::get_rate_down_limit();
+ res.limit_up = epee::net_utils::connection_basic::get_rate_up_limit();
+ res.status = CORE_RPC_STATUS_OK;
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res)
{
- // TODO
- /*if (m_p2p.get_outgoing_connections_count() > req.out_peers)
- {
- m_p2p.m_config.m_net_config.connections_count = req.out_peers;
- if (m_p2p.get_outgoing_connections_count() > req.out_peers)
- {
- int count = m_p2p.get_outgoing_connections_count() - req.out_peers;
- m_p2p.delete_connections(count);
- }
- }
-
- else
- m_p2p.m_config.m_net_config.connections_count = req.out_peers;
- */
- return true;
+ size_t n_connections = m_p2p.get_outgoing_connections_count();
+ size_t n_delete = (n_connections > req.out_peers) ? n_connections - req.out_peers : 0;
+ m_p2p.m_config.m_net_config.connections_count = req.out_peers;
+ if (n_delete)
+ m_p2p.delete_connections(n_delete);
+ res.status = CORE_RPC_STATUS_OK;
+ return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_start_save_graph(const COMMAND_RPC_START_SAVE_GRAPH::request& req, COMMAND_RPC_START_SAVE_GRAPH::response& res)
@@ -1560,8 +1624,10 @@ namespace cryptonote
static const char software[] = "monero";
#ifdef BUILD_TAG
static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG);
+ static const char subdir[] = "cli";
#else
static const char buildtag[] = "source";
+ static const char subdir[] = "source";
#endif
if (req.command != "check" && req.command != "download" && req.command != "update")
@@ -1584,8 +1650,8 @@ namespace cryptonote
}
res.update = true;
res.version = version;
- res.user_uri = tools::get_update_url(software, "cli", buildtag, version, true);
- res.auto_uri = tools::get_update_url(software, "cli", buildtag, version, false);
+ res.user_uri = tools::get_update_url(software, subdir, buildtag, version, true);
+ res.auto_uri = tools::get_update_url(software, subdir, buildtag, version, false);
res.hash = hash;
if (req.command == "check")
{
@@ -1703,11 +1769,7 @@ namespace cryptonote
}
crypto::hash top_hash;
- if (!m_core.get_blockchain_top(res.height, top_hash))
- {
- res.status = "Failed";
- return false;
- }
+ m_core.get_blockchain_top(res.height, top_hash);
++res.height; // turn top block height into blockchain height
res.target_height = m_core.get_target_blockchain_height();
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index dbbe07972..73a308a72 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -97,6 +97,8 @@ namespace cryptonote
MAP_URI_AUTO_JON2("/get_transaction_pool_stats", on_get_transaction_pool_stats, COMMAND_RPC_GET_TRANSACTION_POOL_STATS)
MAP_URI_AUTO_JON2_IF("/stop_daemon", on_stop_daemon, COMMAND_RPC_STOP_DAEMON, !m_restricted)
MAP_URI_AUTO_JON2("/getinfo", on_get_info, COMMAND_RPC_GET_INFO)
+ MAP_URI_AUTO_JON2("/get_limit", on_get_limit, COMMAND_RPC_GET_LIMIT)
+ MAP_URI_AUTO_JON2_IF("/set_limit", on_set_limit, COMMAND_RPC_SET_LIMIT, !m_restricted)
MAP_URI_AUTO_JON2_IF("/out_peers", on_out_peers, COMMAND_RPC_OUT_PEERS, !m_restricted)
MAP_URI_AUTO_JON2_IF("/start_save_graph", on_start_save_graph, COMMAND_RPC_START_SAVE_GRAPH, !m_restricted)
MAP_URI_AUTO_JON2_IF("/stop_save_graph", on_stop_save_graph, COMMAND_RPC_STOP_SAVE_GRAPH, !m_restricted)
@@ -155,6 +157,8 @@ namespace cryptonote
bool on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res);
bool on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res);
bool on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res);
+ bool on_get_limit(const COMMAND_RPC_GET_LIMIT::request& req, COMMAND_RPC_GET_LIMIT::response& res);
+ bool on_set_limit(const COMMAND_RPC_SET_LIMIT::request& req, COMMAND_RPC_SET_LIMIT::response& res);
bool on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res);
bool on_start_save_graph(const COMMAND_RPC_START_SAVE_GRAPH::request& req, COMMAND_RPC_START_SAVE_GRAPH::response& res);
bool on_stop_save_graph(const COMMAND_RPC_STOP_SAVE_GRAPH::request& req, COMMAND_RPC_STOP_SAVE_GRAPH::response& res);
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 88327dd75..ee2a79eb4 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -49,7 +49,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 1
-#define CORE_RPC_VERSION_MINOR 13
+#define CORE_RPC_VERSION_MINOR 15
#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)
@@ -195,6 +195,358 @@ namespace cryptonote
};
//-----------------------------------------------
+ struct COMMAND_RPC_GET_ADDRESS_TXS
+ {
+ struct request
+ {
+ std::string address;
+ std::string view_key;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct spent_output {
+ uint64_t amount;
+ std::string key_image;
+ std::string tx_pub_key;
+ uint64_t out_index;
+ uint32_t mixin;
+
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(key_image)
+ KV_SERIALIZE(tx_pub_key)
+ KV_SERIALIZE(out_index)
+ KV_SERIALIZE(mixin)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct transaction
+ {
+ uint64_t id;
+ std::string hash;
+ uint64_t timestamp;
+ uint64_t total_received;
+ uint64_t total_sent;
+ uint64_t unlock_time;
+ uint64_t height;
+ std::list<spent_output> spent_outputs;
+ std::string payment_id;
+ bool coinbase;
+ bool mempool;
+ uint32_t mixin;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(id)
+ KV_SERIALIZE(hash)
+ KV_SERIALIZE(timestamp)
+ KV_SERIALIZE(total_received)
+ KV_SERIALIZE(total_sent)
+ KV_SERIALIZE(unlock_time)
+ KV_SERIALIZE(height)
+ KV_SERIALIZE(spent_outputs)
+ KV_SERIALIZE(payment_id)
+ KV_SERIALIZE(coinbase)
+ KV_SERIALIZE(mempool)
+ KV_SERIALIZE(mixin)
+ END_KV_SERIALIZE_MAP()
+ };
+
+
+ struct response
+ {
+ //std::list<std::string> txs_as_json;
+ uint64_t total_received;
+ uint64_t total_received_unlocked = 0; // OpenMonero only
+ uint64_t scanned_height;
+ std::list<transaction> transactions;
+ uint64_t blockchain_height;
+ uint64_t scanned_block_height;
+ std::string status;
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(total_received)
+ KV_SERIALIZE(total_received_unlocked)
+ KV_SERIALIZE(scanned_height)
+ KV_SERIALIZE(transactions)
+ KV_SERIALIZE(blockchain_height)
+ KV_SERIALIZE(scanned_block_height)
+ KV_SERIALIZE(status)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ //-----------------------------------------------
+ struct COMMAND_RPC_GET_ADDRESS_INFO
+ {
+ struct request
+ {
+ std::string address;
+ std::string view_key;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct spent_output
+ {
+ uint64_t amount;
+ std::string key_image;
+ std::string tx_pub_key;
+ uint64_t out_index;
+ uint32_t mixin;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(key_image)
+ KV_SERIALIZE(tx_pub_key)
+ KV_SERIALIZE(out_index)
+ KV_SERIALIZE(mixin)
+ END_KV_SERIALIZE_MAP()
+ };
+
+
+
+ struct response
+ {
+ uint64_t locked_funds;
+ uint64_t total_received;
+ uint64_t total_sent;
+ uint64_t scanned_height;
+ uint64_t scanned_block_height;
+ uint64_t start_height;
+ uint64_t transaction_height;
+ uint64_t blockchain_height;
+ std::list<spent_output> spent_outputs;
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(locked_funds)
+ KV_SERIALIZE(total_received)
+ KV_SERIALIZE(total_sent)
+ KV_SERIALIZE(scanned_height)
+ KV_SERIALIZE(scanned_block_height)
+ KV_SERIALIZE(start_height)
+ KV_SERIALIZE(transaction_height)
+ KV_SERIALIZE(blockchain_height)
+ KV_SERIALIZE(spent_outputs)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ //-----------------------------------------------
+ struct COMMAND_RPC_GET_UNSPENT_OUTS
+ {
+ struct request
+ {
+ std::string amount;
+ std::string address;
+ std::string view_key;
+ // OpenMonero specific
+ uint64_t mixin;
+ bool use_dust;
+ std::string dust_threshold;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ KV_SERIALIZE(mixin)
+ KV_SERIALIZE(use_dust)
+ KV_SERIALIZE(dust_threshold)
+ END_KV_SERIALIZE_MAP()
+ };
+
+
+ struct output {
+ uint64_t amount;
+ std::string public_key;
+ uint64_t index;
+ uint64_t global_index;
+ std::string rct;
+ std::string tx_hash;
+ std::string tx_pub_key;
+ std::string tx_prefix_hash;
+ std::vector<std::string> spend_key_images;
+ uint64_t timestamp;
+ uint64_t height;
+
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(public_key)
+ KV_SERIALIZE(index)
+ KV_SERIALIZE(global_index)
+ KV_SERIALIZE(rct)
+ KV_SERIALIZE(tx_hash)
+ KV_SERIALIZE(tx_pub_key)
+ KV_SERIALIZE(tx_prefix_hash)
+ KV_SERIALIZE(spend_key_images)
+ KV_SERIALIZE(timestamp)
+ KV_SERIALIZE(height)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ uint64_t amount;
+ std::list<output> outputs;
+ uint64_t per_kb_fee;
+ std::string status;
+ std::string reason;
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(outputs)
+ KV_SERIALIZE(per_kb_fee)
+ KV_SERIALIZE(status)
+ KV_SERIALIZE(reason)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ //-----------------------------------------------
+ struct COMMAND_RPC_GET_RANDOM_OUTS
+ {
+ struct request
+ {
+ std::vector<std::string> amounts;
+ uint32_t count;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amounts)
+ KV_SERIALIZE(count)
+ END_KV_SERIALIZE_MAP()
+ };
+
+
+ struct output {
+ std::string public_key;
+ uint64_t global_index;
+ std::string rct; // 64+64+64 characters long (<rct commit> + <encrypted mask> + <rct amount>)
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(public_key)
+ KV_SERIALIZE(global_index)
+ KV_SERIALIZE(rct)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct amount_out {
+ uint64_t amount;
+ std::vector<output> outputs;
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(outputs)
+ END_KV_SERIALIZE_MAP()
+
+ };
+
+ struct response
+ {
+ std::vector<amount_out> amount_outs;
+ std::string Error;
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount_outs)
+ KV_SERIALIZE(Error)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+ //-----------------------------------------------
+ struct COMMAND_RPC_SUBMIT_RAW_TX
+ {
+ struct request
+ {
+ std::string address;
+ std::string view_key;
+ std::string tx;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ KV_SERIALIZE(tx)
+ END_KV_SERIALIZE_MAP()
+ };
+
+
+ struct response
+ {
+ std::string status;
+ std::string error;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(status)
+ KV_SERIALIZE(error)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+ //-----------------------------------------------
+ struct COMMAND_RPC_LOGIN
+ {
+ struct request
+ {
+ std::string address;
+ std::string view_key;
+ bool create_account;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ KV_SERIALIZE(create_account)
+ END_KV_SERIALIZE_MAP()
+ };
+
+
+ struct response
+ {
+ std::string status;
+ std::string reason;
+ bool new_address;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(status)
+ KV_SERIALIZE(reason)
+ KV_SERIALIZE(new_address)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+ //-----------------------------------------------
+ struct COMMAND_RPC_IMPORT_WALLET_REQUEST
+ {
+ struct request
+ {
+ std::string address;
+ std::string view_key;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ END_KV_SERIALIZE_MAP()
+ };
+
+
+ struct response
+ {
+ std::string payment_id;
+ uint64_t import_fee;
+ bool new_request;
+ bool request_fulfilled;
+ std::string payment_address;
+ std::string status;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(payment_id)
+ KV_SERIALIZE(import_fee)
+ KV_SERIALIZE(new_request)
+ KV_SERIALIZE(request_fulfilled)
+ KV_SERIALIZE(payment_address)
+ KV_SERIALIZE(status)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+ //-----------------------------------------------
struct COMMAND_RPC_GET_TRANSACTIONS
{
struct request
@@ -215,6 +567,7 @@ namespace cryptonote
std::string as_json;
bool in_pool;
uint64_t block_height;
+ uint64_t block_timestamp;
std::vector<uint64_t> output_indices;
BEGIN_KV_SERIALIZE_MAP()
@@ -223,6 +576,7 @@ namespace cryptonote
KV_SERIALIZE(as_json)
KV_SERIALIZE(in_pool)
KV_SERIALIZE(block_height)
+ KV_SERIALIZE(block_timestamp)
KV_SERIALIZE(output_indices)
END_KV_SERIALIZE_MAP()
};
@@ -979,8 +1333,11 @@ namespace cryptonote
struct response
{
std::string status;
+ std::string categories;
+
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
+ KV_SERIALIZE(categories)
END_KV_SERIALIZE_MAP()
};
};
@@ -1242,6 +1599,55 @@ namespace cryptonote
};
};
+ struct COMMAND_RPC_GET_LIMIT
+ {
+ struct request
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string status;
+ uint64_t limit_up;
+ uint64_t limit_down;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(status)
+ KV_SERIALIZE(limit_up)
+ KV_SERIALIZE(limit_down)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_SET_LIMIT
+ {
+ struct request
+ {
+ int64_t limit_down;
+ int64_t limit_up;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(limit_down)
+ KV_SERIALIZE(limit_up)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string status;
+ uint64_t limit_up;
+ uint64_t limit_down;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(status)
+ KV_SERIALIZE(limit_up)
+ KV_SERIALIZE(limit_down)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
struct COMMAND_RPC_OUT_PEERS
{
struct request
diff --git a/src/rpc/core_rpc_server_error_codes.h b/src/rpc/core_rpc_server_error_codes.h
index 269cce2b1..bd90d37aa 100644
--- a/src/rpc/core_rpc_server_error_codes.h
+++ b/src/rpc/core_rpc_server_error_codes.h
@@ -41,5 +41,6 @@
#define CORE_RPC_ERROR_CODE_CORE_BUSY -9
#define CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE -10
#define CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC -11
+#define CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS -12
diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp
index 53eeb5e76..4d3fbf491 100644
--- a/src/rpc/daemon_handler.cpp
+++ b/src/rpc/daemon_handler.cpp
@@ -412,14 +412,21 @@ namespace rpc
void DaemonHandler::handle(const StartMining::Request& req, StartMining::Response& res)
{
- account_public_address adr;
- if(!get_account_address_from_str(adr, m_core.get_testnet(), req.miner_address))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, m_core.get_testnet(), req.miner_address))
{
res.error_details = "Failed, wrong address";
LOG_PRINT_L0(res.error_details);
res.status = Message::STATUS_FAILED;
return;
}
+ if (info.is_subaddress)
+ {
+ res.error_details = "Failed, mining to subaddress isn't supported yet";
+ LOG_PRINT_L0(res.error_details);
+ res.status = Message::STATUS_FAILED;
+ return;
+ }
unsigned int concurrency_count = boost::thread::hardware_concurrency() * 4;
@@ -442,7 +449,7 @@ namespace rpc
boost::thread::attributes attrs;
attrs.set_stack_size(THREAD_STACK_SIZE);
- if(!m_core.get_miner().start(adr, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery))
+ if(!m_core.get_miner().start(info.address, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery))
{
res.error_details = "Failed, mining not started";
LOG_PRINT_L0(res.error_details);
@@ -518,7 +525,7 @@ namespace rpc
res.speed = lMiner.get_speed();
res.threads_count = lMiner.get_threads_count();
const account_public_address& lMiningAdr = lMiner.get_mining_address();
- res.address = get_account_address_as_str(m_core.get_testnet(), lMiningAdr);
+ res.address = get_account_address_as_str(m_core.get_testnet(), false, lMiningAdr);
}
res.status = Message::STATUS_OK;
@@ -542,7 +549,7 @@ namespace rpc
{
if (m_core.get_current_blockchain_height() <= req.height)
{
- res.hash = cryptonote::null_hash;
+ res.hash = crypto::null_hash;
res.status = Message::STATUS_FAILED;
res.error_details = "height given is higher than current chain height";
return;
diff --git a/src/rpc/message.cpp b/src/rpc/message.cpp
index 086820180..d6da124d1 100644
--- a/src/rpc/message.cpp
+++ b/src/rpc/message.cpp
@@ -45,6 +45,15 @@ const char* Message::STATUS_FAILED = "Failed";
const char* Message::STATUS_BAD_REQUEST = "Invalid request type";
const char* Message::STATUS_BAD_JSON = "Malformed json";
+namespace
+{
+constexpr const char error_field[] = "error";
+constexpr const char id_field[] = "id";
+constexpr const char method_field[] = "method";
+constexpr const char params_field[] = "params";
+constexpr const char result_field[] = "result";
+}
+
rapidjson::Value Message::toJson(rapidjson::Document& doc) const
{
rapidjson::Value val(rapidjson::kObjectType);
@@ -70,8 +79,8 @@ FullMessage::FullMessage(const std::string& request, Message* message)
{
doc.SetObject();
- doc.AddMember("method", rapidjson::StringRef(request.c_str()), doc.GetAllocator());
- doc.AddMember("params", message->toJson(doc), doc.GetAllocator());
+ doc.AddMember(method_field, rapidjson::StringRef(request.c_str()), doc.GetAllocator());
+ doc.AddMember(params_field, message->toJson(doc), doc.GetAllocator());
// required by JSON-RPC 2.0 spec
doc.AddMember("jsonrpc", rapidjson::Value("2.0"), doc.GetAllocator());
@@ -86,7 +95,7 @@ FullMessage::FullMessage(Message* message)
if (message->status == Message::STATUS_OK)
{
- doc.AddMember("response", message->toJson(doc), doc.GetAllocator());
+ doc.AddMember(result_field, message->toJson(doc), doc.GetAllocator());
}
else
{
@@ -111,14 +120,14 @@ FullMessage::FullMessage(const std::string& json_string, bool request)
if (request)
{
- OBJECT_HAS_MEMBER_OR_THROW(doc, "method")
- OBJECT_HAS_MEMBER_OR_THROW(doc, "params")
+ OBJECT_HAS_MEMBER_OR_THROW(doc, method_field)
+ OBJECT_HAS_MEMBER_OR_THROW(doc, params_field)
}
else
{
- if (!doc.HasMember("response") && !doc.HasMember("error"))
+ if (!doc.HasMember(result_field) && !doc.HasMember(error_field))
{
- throw cryptonote::json::MISSING_KEY("error/response");
+ throw cryptonote::json::MISSING_KEY("error/result");
}
}
}
@@ -126,9 +135,9 @@ FullMessage::FullMessage(const std::string& json_string, bool request)
std::string FullMessage::getJson()
{
- if (!doc.HasMember("id"))
+ if (!doc.HasMember(id_field))
{
- doc.AddMember("id", rapidjson::Value("unused"), doc.GetAllocator());
+ doc.AddMember(id_field, rapidjson::Value("unused"), doc.GetAllocator());
}
rapidjson::StringBuffer buf;
@@ -142,24 +151,24 @@ std::string FullMessage::getJson()
std::string FullMessage::getRequestType() const
{
- OBJECT_HAS_MEMBER_OR_THROW(doc, "method")
- return doc["method"].GetString();
+ OBJECT_HAS_MEMBER_OR_THROW(doc, method_field)
+ return doc[method_field].GetString();
}
rapidjson::Value& FullMessage::getMessage()
{
- if (doc.HasMember("params"))
+ if (doc.HasMember(params_field))
{
- return doc["params"];
+ return doc[params_field];
}
- else if (doc.HasMember("response"))
+ else if (doc.HasMember(result_field))
{
- return doc["response"];
+ return doc[result_field];
}
//else
- OBJECT_HAS_MEMBER_OR_THROW(doc, "error")
- return doc["error"];
+ OBJECT_HAS_MEMBER_OR_THROW(doc, error_field)
+ return doc[error_field];
}
@@ -172,20 +181,20 @@ rapidjson::Value FullMessage::getMessageCopy()
rapidjson::Value& FullMessage::getID()
{
- OBJECT_HAS_MEMBER_OR_THROW(doc, "id")
- return doc["id"];
+ OBJECT_HAS_MEMBER_OR_THROW(doc, id_field)
+ return doc[id_field];
}
void FullMessage::setID(rapidjson::Value& id)
{
- auto itr = doc.FindMember("id");
+ auto itr = doc.FindMember(id_field);
if (itr != doc.MemberEnd())
{
itr->value = id;
}
else
{
- doc.AddMember("id", id, doc.GetAllocator());
+ doc.AddMember(id_field, id, doc.GetAllocator());
}
}
@@ -193,7 +202,7 @@ cryptonote::rpc::error FullMessage::getError()
{
cryptonote::rpc::error err;
err.use = false;
- if (doc.HasMember("error"))
+ if (doc.HasMember(error_field))
{
GET_FROM_JSON_OBJECT(doc, err, error);
err.use = true;
diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp
index 4435f74d1..93309bf3c 100644..100755
--- a/src/rpc/rpc_args.cpp
+++ b/src/rpc/rpc_args.cpp
@@ -28,6 +28,7 @@
//
#include "rpc_args.h"
+#include <boost/algorithm/string.hpp>
#include <boost/asio/ip/address.hpp>
#include "common/command_line.h"
#include "common/i18n.h"
@@ -38,6 +39,7 @@ namespace cryptonote
: rpc_bind_ip({"rpc-bind-ip", rpc_args::tr("Specify ip to bind rpc server"), "127.0.0.1"})
, rpc_login({"rpc-login", rpc_args::tr("Specify username[:password] required for RPC server"), "", true})
, confirm_external_bind({"confirm-external-bind", rpc_args::tr("Confirm rpc-bind-ip value is NOT a loopback (local) IP")})
+ , rpc_access_control_origins({"rpc-access-control-origins", rpc_args::tr("Specify a comma separated list of origins to allow cross origin resource sharing"), ""})
{}
const char* rpc_args::tr(const char* str) { return i18n_translate(str, "cryptonote::rpc_args"); }
@@ -48,6 +50,7 @@ namespace cryptonote
command_line::add_arg(desc, arg.rpc_bind_ip);
command_line::add_arg(desc, arg.rpc_login);
command_line::add_arg(desc, arg.confirm_external_bind);
+ command_line::add_arg(desc, arg.rpc_access_control_origins);
}
boost::optional<rpc_args> rpc_args::process(const boost::program_options::variables_map& vm)
@@ -91,6 +94,21 @@ namespace cryptonote
}
}
+ auto access_control_origins_input = command_line::get_arg(vm, arg.rpc_access_control_origins);
+ if (!access_control_origins_input.empty())
+ {
+ if (!config.login)
+ {
+ LOG_ERROR(arg.rpc_access_control_origins.name << tr(" requires RFC server password --") << arg.rpc_login.name << tr(" cannot be empty"));
+ return boost::none;
+ }
+
+ std::vector<std::string> access_control_origins;
+ boost::split(access_control_origins, access_control_origins_input, boost::is_any_of(","));
+ std::for_each(access_control_origins.begin(), access_control_origins.end(), boost::bind(&boost::trim<std::string>, _1, std::locale::classic()));
+ config.access_control_origins = std::move(access_control_origins);
+ }
+
return {std::move(config)};
}
}
diff --git a/src/rpc/rpc_args.h b/src/rpc/rpc_args.h
index d6e7bab07..72b5aa706 100644..100755
--- a/src/rpc/rpc_args.h
+++ b/src/rpc/rpc_args.h
@@ -53,6 +53,7 @@ namespace cryptonote
const command_line::arg_descriptor<std::string> rpc_bind_ip;
const command_line::arg_descriptor<std::string> rpc_login;
const command_line::arg_descriptor<bool> confirm_external_bind;
+ const command_line::arg_descriptor<std::string> rpc_access_control_origins;
};
static const char* tr(const char* str);
@@ -62,6 +63,7 @@ namespace cryptonote
static boost::optional<rpc_args> process(const boost::program_options::variables_map& vm);
std::string bind_ip;
+ std::vector<std::string> access_control_origins;
boost::optional<tools::login> login; // currently `boost::none` if unspecified by user
};
}
diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp
index ead3fdd58..a40821d19 100644
--- a/src/serialization/json_object.cpp
+++ b/src/serialization/json_object.cpp
@@ -29,6 +29,7 @@
#include "json_object.h"
#include <limits>
+#include <type_traits>
#include "string_tools.h"
namespace cryptonote
@@ -52,8 +53,9 @@ namespace
void convert_numeric(Source source, Type& i)
{
static_assert(
+ (std::is_same<Type, char>() && std::is_same<Source, int>()) ||
std::numeric_limits<Source>::is_signed == std::numeric_limits<Type>::is_signed,
- "source and destination signs do not match"
+ "comparisons below may have undefined behavior"
);
if (source < std::numeric_limits<Type>::min())
{
@@ -774,6 +776,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::tx_in_pool& tx)
GET_FROM_JSON_OBJECT(val, tx.receive_time, receive_time);
GET_FROM_JSON_OBJECT(val, tx.last_relayed_time, last_relayed_time);
GET_FROM_JSON_OBJECT(val, tx.relayed, relayed);
+ GET_FROM_JSON_OBJECT(val, tx.do_not_relay, do_not_relay);
}
void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val)
diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h
index 639240820..869f5d10e 100644
--- a/src/serialization/serialization.h
+++ b/src/serialization/serialization.h
@@ -41,6 +41,7 @@
#pragma once
#include <vector>
+#include <deque>
#include <list>
#include <string>
#include <boost/type_traits/is_integral.hpp>
@@ -198,6 +199,11 @@ inline bool do_serialize(Archive &ar, bool &v)
#define PREPARE_CUSTOM_VECTOR_SERIALIZATION(size, vec) \
::serialization::detail::prepare_custom_vector_serialization(size, vec, typename Archive<W>::is_saving())
+/*! \macro PREPARE_CUSTOM_DEQUE_SERIALIZATION
+ */
+#define PREPARE_CUSTOM_DEQUE_SERIALIZATION(size, vec) \
+ ::serialization::detail::prepare_custom_deque_serialization(size, vec, typename Archive<W>::is_saving())
+
/*! \macro END_SERIALIZE
* \brief self-explanatory
*/
@@ -292,6 +298,17 @@ namespace serialization {
vec.resize(size);
}
+ template <typename T>
+ void prepare_custom_deque_serialization(size_t size, std::deque<T>& vec, const boost::mpl::bool_<true>& /*is_saving*/)
+ {
+ }
+
+ template <typename T>
+ void prepare_custom_deque_serialization(size_t size, std::deque<T>& vec, const boost::mpl::bool_<false>& /*is_saving*/)
+ {
+ vec.resize(size);
+ }
+
/*! \fn do_check_stream_state
*
* \brief self explanatory
diff --git a/src/serialization/vector.h b/src/serialization/vector.h
index 598cfb92e..12fd59558 100644
--- a/src/serialization/vector.h
+++ b/src/serialization/vector.h
@@ -37,6 +37,11 @@ bool do_serialize(Archive<false> &ar, std::vector<T> &v);
template <template <bool> class Archive, class T>
bool do_serialize(Archive<true> &ar, std::vector<T> &v);
+template <template <bool> class Archive, class T>
+bool do_serialize(Archive<false> &ar, std::deque<T> &v);
+template <template <bool> class Archive, class T>
+bool do_serialize(Archive<true> &ar, std::deque<T> &v);
+
namespace serialization
{
namespace detail
@@ -64,7 +69,7 @@ namespace serialization
}
template <template <bool> class Archive, class T>
-bool do_serialize(Archive<false> &ar, std::vector<T> &v)
+bool do_serialize_vd(Archive<false> &ar, T &v)
{
size_t cnt;
ar.begin_array(cnt);
@@ -93,7 +98,7 @@ bool do_serialize(Archive<false> &ar, std::vector<T> &v)
}
template <template <bool> class Archive, class T>
-bool do_serialize(Archive<true> &ar, std::vector<T> &v)
+bool do_serialize_vd(Archive<true> &ar, T &v)
{
size_t cnt = v.size();
ar.begin_array(cnt);
@@ -110,3 +115,13 @@ bool do_serialize(Archive<true> &ar, std::vector<T> &v)
ar.end_array();
return true;
}
+
+template <template <bool> class Archive, class T>
+bool do_serialize(Archive<false> &ar, std::vector<T> &v) { return do_serialize_vd(ar, v); }
+template <template <bool> class Archive, class T>
+bool do_serialize(Archive<true> &ar, std::vector<T> &v) { return do_serialize_vd(ar, v); }
+
+template <template <bool> class Archive, class T>
+bool do_serialize(Archive<false> &ar, std::deque<T> &v) { return do_serialize_vd(ar, v); }
+template <template <bool> class Archive, class T>
+bool do_serialize(Archive<true> &ar, std::deque<T> &v) { return do_serialize_vd(ar, v); }
diff --git a/src/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt
index 443e9b87e..b56085b8f 100644
--- a/src/simplewallet/CMakeLists.txt
+++ b/src/simplewallet/CMakeLists.txt
@@ -49,14 +49,13 @@ target_link_libraries(simplewallet
common
mnemonics
p2p
+ version
${Boost_CHRONO_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})
-add_dependencies(simplewallet
- version)
set_property(TARGET simplewallet
PROPERTY
OUTPUT_NAME "monero-wallet-cli")
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 857e2af6e..b55b39236 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -121,6 +121,7 @@ namespace
const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", sw::tr("Enable commands which rely on a trusted daemon"), false};
const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false};
const command_line::arg_descriptor<uint64_t> arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0};
+ const command_line::arg_descriptor<bool> arg_do_not_relay = {"do-not-relay", sw::tr("The newly created transaction will not be relayed to the monero network"), false};
const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""};
@@ -251,6 +252,30 @@ namespace
}
return addresses[0];
}
+
+ bool parse_subaddress_indices(const std::string& arg, std::set<uint32_t>& subaddr_indices)
+ {
+ subaddr_indices.clear();
+
+ if (arg.substr(0, 6) != "index=")
+ return false;
+ std::string subaddr_indices_str_unsplit = arg.substr(6, arg.size() - 6);
+ std::vector<std::string> subaddr_indices_str;
+ boost::split(subaddr_indices_str, subaddr_indices_str_unsplit, boost::is_any_of(","));
+
+ for (const auto& subaddr_index_str : subaddr_indices_str)
+ {
+ uint32_t subaddr_index;
+ if(!epee::string_tools::get_xtype_from_string(subaddr_index, subaddr_index_str))
+ {
+ fail_msg_writer() << tr("failed to parse index: ") << subaddr_index_str;
+ subaddr_indices.clear();
+ return false;
+ }
+ subaddr_indices.insert(subaddr_index);
+ }
+ return true;
+ }
}
@@ -290,7 +315,7 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto
return true;
}
-bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+bool simple_wallet::print_seed(bool encrypted)
{
bool success = false;
std::string electrum_words;
@@ -311,7 +336,16 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st
m_wallet->set_seed_language(mnemonic_language);
}
- success = m_wallet->get_seed(electrum_words);
+ std::string seed_pass;
+ if (encrypted)
+ {
+ auto pwd_container = tools::password_container::prompt(true, tr("Enter optional seed encryption passphrase, empty to see raw seed"));
+ if (std::cin.eof() || !pwd_container)
+ return true;
+ seed_pass = pwd_container->password();
+ }
+
+ success = m_wallet->get_seed(electrum_words, seed_pass);
}
if (success)
@@ -325,6 +359,16 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st
return true;
}
+bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ return print_seed(false);
+}
+
+bool simple_wallet::encrypted_seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ return print_seed(true);
+}
+
bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if (m_wallet->watch_only())
@@ -505,8 +549,6 @@ bool simple_wallet::set_default_ring_size(const std::vector<std::string> &args/*
fail_msg_writer() << tr("ring size must be an integer >= 3");
return true;
}
- if (ring_size == 0)
- ring_size = DEFAULT_MIX + 1;
const auto pwd_container = get_and_verify_password();
if (pwd_container)
@@ -716,6 +758,41 @@ bool simple_wallet::set_confirm_backlog(const std::vector<std::string> &args/* =
return true;
}
+bool simple_wallet::set_confirm_backlog_threshold(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ uint32_t threshold;
+ if (!string_tools::get_xtype_from_string(threshold, args[1]))
+ {
+ fail_msg_writer() << tr("invalid count: must be an unsigned integer");
+ return true;
+ }
+
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ m_wallet->set_confirm_backlog_threshold(threshold);
+ m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ }
+ return true;
+}
+
+bool simple_wallet::set_refresh_from_block_height(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ uint64_t height;
+ if (!epee::string_tools::get_xtype_from_string(height, args[1]))
+ {
+ fail_msg_writer() << tr("Invalid height");
+ return true;
+ }
+ m_wallet->set_refresh_from_block_height(height);
+ 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>()*/)
{
success_msg_writer() << get_commands_str();
@@ -729,26 +806,28 @@ simple_wallet::simple_wallet()
, m_auto_refresh_enabled(false)
, m_auto_refresh_refreshing(false)
, m_in_manual_refresh(false)
+ , m_current_subaddress_account(0)
{
- m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), tr("start_mining [<number_of_threads>] - Start mining in daemon"));
+ m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), tr("start_mining [<number_of_threads>] [bg_mining] [ignore_battery] - Start mining in daemon (bg_mining and ignore_battery are optional booleans)"));
m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), tr("Stop mining in daemon"));
m_cmd_binder.set_handler("save_bc", boost::bind(&simple_wallet::save_bc, this, _1), tr("Save current blockchain data"));
m_cmd_binder.set_handler("refresh", boost::bind(&simple_wallet::refresh, this, _1), tr("Synchronize transactions and balance"));
- m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), tr("Show current wallet balance"));
- m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), tr("incoming_transfers [available|unavailable] - Show incoming transfers, all or filtered by availability"));
+ m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), tr("balance [detail] - Show wallet balance of currently selected account"));
+ m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), tr("incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>,...]] - Show incoming transfers, all or filtered by availability and address index"));
m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), tr("payments <PID_1> [<PID_2> ... <PID_N>] - Show payments for given payment ID[s]"));
m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height"));
m_cmd_binder.set_handler("transfer_original", boost::bind(&simple_wallet::transfer, this, _1), tr("Same as transfer, but using an older transaction building algorithm"));
- m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("transfer [<priority>] [<ring_size>] <address> <amount> [<payment_id>] - Transfer <amount> to <address>. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. 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 <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), tr("locked_transfer [<ring_size>] <addr> <amount> <lockblocks>(Number of blocks to lock the transaction for, max 1000000) [<payment_id>]"));
+ m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>] - 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 fee of the transaction. 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 <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), tr("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <addr> <amount> <lockblocks> [<payment_id>] - Same as transfer, but with number of blocks to lock the transaction for, max 1000000"));
m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _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("sweep_all [ring_size] address [payment_id] - Send all unlocked balance to an address"));
- m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below <amount_threshold> [ring_size] address [payment_id] - Send all unlocked outputs below the threshold to an address"));
- m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), tr("donate [<ring_size>] <amount> [payment_id] - Donate <amount> to the development team (donate.getmonero.org)"));
+ m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("sweep_all [index=<N1>[,<N2>,...]] [<ring_size>] <address> [<payment_id>] - 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."));
+ m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<ring_size>] <address> [<payment_id>] - Send all unlocked outputs below the threshold to an address"));
+ m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), tr("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>] - Donate <amount> to the development team (donate.getmonero.org)"));
m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), tr("Sign a transaction from a file"));
m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file"));
- m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level>|<categories> - Change current log detail (level must be <0-4>)"));
- m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address"));
+ m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level>|{+,-,}<categories> - Change current log detail (level must be <0-4>)"));
+ m_cmd_binder.set_handler("account", boost::bind(&simple_wallet::account, this, _1), tr("account [new <label text with white spaces allowed> | switch <index> | label <index> <label text with white spaces allowed>] - If no argments are specified, the wallet shows all the existing accounts along with their balances. 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). If the \"switch\" argument is specified, the wallet switches to the account specified by <index>. If the \"label\" argument is specified, the wallet sets the label of the account specified by <index> to the provided label text."));
+ m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> ] - If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the walllet 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), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID"));
m_cmd_binder.set_handler("address_book", boost::bind(&simple_wallet::address_book, this, _1), tr("address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)] - Print all entries in the address book, optionally adding/deleting an entry to/from it"));
m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), tr("Save wallet data"));
@@ -756,18 +835,20 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), tr("Display private view key"));
m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Display private spend key"));
m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Display Electrum-style mnemonic seed"));
- m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-ring-size <n> - set default ring size (default is 5); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - set default monero (sub-)unit; min-outputs-count [n] - try to keep at least that many outputs of value at least min-outputs-value; min-outputs-value [n] - try to keep at least min-outputs-count outputs of at least that value; merge-destinations <1|0> - whether to merge multiple payments to the same destination address; confirm-backlog <1|0> - whether to warn if there is transaction backlog"));
+ m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-ring-size <n> - set default ring size (default is 5); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - set default monero (sub-)unit; min-outputs-count [n] - try to keep at least that many outputs of value at least min-outputs-value; min-outputs-value [n] - try to keep at least min-outputs-count outputs of at least that value; merge-destinations <1|0> - whether to merge multiple payments to the same destination address; confirm-backlog <1|0> - whether to warn if there is transaction backlog; confirm-backlog-threshold [n] - sets a threshold for confirm-backlog to only warn if the transaction backlog is greater than n blocks; refresh-from-block-height [n] - set height before which to ignore blocks"));
+ m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::encrypted_seed, this, _1), tr("Display encrypted Electrum-style mnemonic seed"));
m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs"));
m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>"));
m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>"));
m_cmd_binder.set_handler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, _1), tr("Generate a signature to prove payment to <address> in <txid> using the transaction secret key (r) without revealing it"));
m_cmd_binder.set_handler("check_tx_proof", boost::bind(&simple_wallet::check_tx_proof, this, _1), tr("Check tx proof for payment going to <address> in <txid>"));
- m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range"));
- m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [<min_amount> <max_amount>] - Show unspent outputs within an optional amount range"));
+ m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range"));
+ m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]] - Show 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), tr("Rescan blockchain from scratch"));
m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("Set an arbitrary string note for a txid"));
m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), tr("Get a string note for a txid"));
m_cmd_binder.set_handler("status", boost::bind(&simple_wallet::status, this, _1), tr("Show wallet status information"));
+ m_cmd_binder.set_handler("wallet_info", boost::bind(&simple_wallet::wallet_info, this, _1), tr("Show wallet information"));
m_cmd_binder.set_handler("sign", boost::bind(&simple_wallet::sign, this, _1), tr("Sign the contents of a file"));
m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::verify, this, _1), 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), tr("Export a signed set of key images"));
@@ -800,6 +881,8 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
success_msg_writer() << "min-outputs-value = " << cryptonote::print_money(m_wallet->get_min_output_value());
success_msg_writer() << "merge-destinations = " << m_wallet->merge_destinations();
success_msg_writer() << "confirm-backlog = " << m_wallet->confirm_backlog();
+ success_msg_writer() << "confirm-backlog-threshold = " << m_wallet->get_confirm_backlog_threshold();
+ success_msg_writer() << "refresh-from-block-height = " << m_wallet->get_refresh_from_block_height();
return true;
}
else
@@ -846,6 +929,8 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
CHECK_SIMPLE_VARIABLE("min-outputs-value", set_min_output_value, tr("amount"));
CHECK_SIMPLE_VARIABLE("merge-destinations", set_merge_destinations, tr("0 or 1"));
CHECK_SIMPLE_VARIABLE("confirm-backlog", set_confirm_backlog, tr("0 or 1"));
+ CHECK_SIMPLE_VARIABLE("confirm-backlog-threshold", set_confirm_backlog_threshold, tr("unsigned integer"));
+ CHECK_SIMPLE_VARIABLE("refresh-from-block-height", set_refresh_from_block_height, tr("block height"));
}
fail_msg_writer() << tr("set: unrecognized argument(s)");
return true;
@@ -854,12 +939,14 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::set_log(const std::vector<std::string> &args)
{
- if(args.size() != 1)
+ if(args.size() > 1)
{
fail_msg_writer() << tr("usage: set_log <log_level_number_0-4> | <categories>");
return true;
}
- mlog_set_log(args[0].c_str());
+ if (!args.empty())
+ mlog_set_log(args[0].c_str());
+ success_msg_writer() << "New log categories: " << mlog_get_categories();
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -1026,6 +1113,13 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("Electrum-style word list failed verification");
return false;
}
+
+ auto pwd_container = tools::password_container::prompt(false, tr("Enter seed encryption passphrase, empty if none"));
+ if (std::cin.eof() || !pwd_container)
+ return false;
+ std::string seed_pass = pwd_container->password();
+ if (!seed_pass.empty())
+ m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass);
}
if (!m_generate_from_view_key.empty())
{
@@ -1038,14 +1132,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("No data supplied, cancelled");
return false;
}
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, tools::wallet2::has_testnet_option(vm), address_string))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, tools::wallet2::has_testnet_option(vm), address_string))
{
fail_msg_writer() << tr("failed to parse address");
return false;
}
+ if (info.is_subaddress)
+ {
+ fail_msg_writer() << tr("This address is a subaddress which cannot be used here.");
+ return false;
+ }
// parse view secret key
std::string viewkey_string = command_line::input_line("View key: ");
@@ -1071,12 +1168,12 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("failed to verify view key secret key");
return false;
}
- if (address.m_view_public_key != pkey) {
+ if (info.address.m_view_public_key != pkey) {
fail_msg_writer() << tr("view key does not match standard address");
return false;
}
- bool r = new_wallet(vm, address, boost::none, viewkey);
+ bool r = new_wallet(vm, info.address, boost::none, viewkey);
CHECK_AND_ASSERT_MES(r, false, tr("account creation failed"));
}
else if (!m_generate_from_keys.empty())
@@ -1090,14 +1187,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("No data supplied, cancelled");
return false;
}
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, tools::wallet2::has_testnet_option(vm), address_string))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, tools::wallet2::has_testnet_option(vm), address_string))
{
fail_msg_writer() << tr("failed to parse address");
return false;
}
+ if (info.is_subaddress)
+ {
+ fail_msg_writer() << tr("This address is a subaddress which cannot be used here.");
+ return false;
+ }
// parse spend secret key
std::string spendkey_string = command_line::input_line("Secret spend key: ");
@@ -1139,7 +1239,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("failed to verify spend key secret key");
return false;
}
- if (address.m_spend_public_key != pkey) {
+ if (info.address.m_spend_public_key != pkey) {
fail_msg_writer() << tr("spend key does not match standard address");
return false;
}
@@ -1147,11 +1247,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("failed to verify view key secret key");
return false;
}
- if (address.m_view_public_key != pkey) {
+ if (info.address.m_view_public_key != pkey) {
fail_msg_writer() << tr("view key does not match standard address");
return false;
}
- bool r = new_wallet(vm, address, spendkey, viewkey);
+ bool r = new_wallet(vm, info.address, spendkey, viewkey);
CHECK_AND_ASSERT_MES(r, false, tr("account creation failed"));
}
@@ -1196,10 +1296,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("No data supplied, cancelled");
return false;
}
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, tools::wallet2::has_testnet_option(vm), address_string))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, tools::wallet2::has_testnet_option(vm), address_string))
{
fail_msg_writer() << tr("failed to parse address");
return false;
@@ -1229,7 +1327,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("failed to verify secret view key");
return false;
}
- if (address.m_view_public_key != pkey)
+ if (info.address.m_view_public_key != pkey)
{
fail_msg_writer() << tr("view key does not match standard address");
return false;
@@ -1280,14 +1378,14 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("failed to verify spend key secret key");
return false;
}
- if (address.m_spend_public_key != pkey)
+ if (info.address.m_spend_public_key != pkey)
{
fail_msg_writer() << tr("spend key does not match standard address");
return false;
}
// create wallet
- bool r = new_wallet(vm, address, spendkey, viewkey);
+ bool r = new_wallet(vm, info.address, spendkey, viewkey);
CHECK_AND_ASSERT_MES(r, false, tr("account creation failed"));
}
@@ -1370,8 +1468,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
}
}
}
- m_wallet->set_refresh_from_block_height(m_restore_height);
}
+ if (m_restoring)
+ m_wallet->set_refresh_from_block_height(m_restore_height);
}
else
{
@@ -1396,6 +1495,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
}
catch (const std::exception &e) { }
+ if (!m_trusted_daemon)
+ message_writer() << (boost::format(tr("Warning: using an untrusted daemon at %s, privacy will be lessened")) % m_wallet->get_daemon_address()).str();
+
m_http_client.set_server(m_wallet->get_daemon_address(), m_wallet->get_daemon_login());
m_wallet->callback(this);
@@ -1425,6 +1527,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_
m_trusted_daemon = command_line::get_arg(vm, arg_trusted_daemon);
m_allow_mismatched_daemon_version = command_line::get_arg(vm, arg_allow_mismatched_daemon_version);
m_restore_height = command_line::get_arg(vm, arg_restore_height);
+ m_do_not_relay = command_line::get_arg(vm, arg_do_not_relay);
m_restoring = !m_generate_from_view_key.empty() ||
!m_generate_from_keys.empty() ||
!m_generate_from_multisig_keys.empty() ||
@@ -1680,9 +1783,18 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm)
catch (const std::exception& e)
{
fail_msg_writer() << tr("failed to load wallet: ") << e.what();
- // only suggest removing cache if the password was actually correct
- if (m_wallet && m_wallet->verify_password(password))
- fail_msg_writer() << boost::format(tr("You may want to remove the file \"%s\" and try again")) % m_wallet_file;
+ if (m_wallet)
+ {
+ // only suggest removing cache if the password was actually correct
+ bool password_is_correct = false;
+ try
+ {
+ password_is_correct = m_wallet->verify_password(password);
+ }
+ catch (...) { } // guard against I/O errors
+ if (password_is_correct)
+ fail_msg_writer() << boost::format(tr("You may want to remove the file \"%s\" and try again")) % m_wallet_file;
+ }
return false;
}
success_msg_writer() <<
@@ -1778,8 +1890,8 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args)
bool ok = true;
size_t max_mining_threads_count = (std::max)(tools::get_max_concurrency(), static_cast<unsigned>(2));
size_t arg_size = args.size();
- if(arg_size >= 3) req.ignore_battery = args[2] == "true";
- if(arg_size >= 2) req.do_background_mining = args[1] == "true";
+ if(arg_size >= 3) req.ignore_battery = is_it_true(args[2]);
+ if(arg_size >= 2) req.do_background_mining = is_it_true(args[1]);
if(arg_size >= 1)
{
uint16_t num = 1;
@@ -1857,29 +1969,31 @@ void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block
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)
+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)
{
message_writer(console_color_green, false) << "\r" <<
tr("Height ") << height << ", " <<
- tr("transaction ") << txid << ", " <<
- tr("received ") << print_money(amount);
+ tr("txid ") << txid << ", " <<
+ print_money(amount) << tr(" XMR, ") <<
+ tr("idx ") << subaddr_index;
if (m_auto_refresh_refreshing)
m_cmd_binder.print_prompt();
else
m_refresh_progress_reporter.update(height, true);
}
//----------------------------------------------------------------------------------------------------
-void simple_wallet::on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount)
+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)
{
// 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)
+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)
{
message_writer(console_color_magenta, false) << "\r" <<
tr("Height ") << height << ", " <<
- tr("transaction ") << txid << ", " <<
- tr("spent ") << print_money(amount);
+ tr("txid ") << txid << ", " <<
+ tr("spent ") << print_money(amount) << ", " <<
+ tr("idx ") << subaddr_index;
if (m_auto_refresh_refreshing)
m_cmd_binder.print_prompt();
else
@@ -1898,7 +2012,7 @@ void simple_wallet::on_skip_transaction(uint64_t height, const crypto::hash &txi
m_refresh_progress_reporter.update(height, true);
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::refresh_main(uint64_t start_height, bool reset)
+bool simple_wallet::refresh_main(uint64_t start_height, bool reset, bool is_init)
{
if (!try_connect_to_daemon())
return true;
@@ -1926,6 +2040,8 @@ bool simple_wallet::refresh_main(uint64_t start_height, bool reset)
// Clear line "Height xxx of xxx"
std::cout << "\r \r";
success_msg_writer(true) << tr("Refresh done, blocks received: ") << fetched_blocks;
+ if (is_init)
+ print_accounts();
show_balance_unlocked();
}
catch (const tools::error::daemon_busy&)
@@ -1986,47 +2102,84 @@ bool simple_wallet::refresh(const std::vector<std::string>& args)
return refresh_main(start_height, false);
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::show_balance_unlocked()
+bool simple_wallet::show_balance_unlocked(bool detailed)
{
- success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance()) << ", "
- << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance());
+ success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0});
+ success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance(m_current_subaddress_account)) << ", "
+ << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account));
+ std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account);
+ std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account);
+ if (!detailed || balance_per_subaddress.empty())
+ return true;
+ success_msg_writer() << tr("Balance per address:");
+ success_msg_writer() << boost::format("%15s %21s %21s %7s %21s") % tr("Address") % tr("Balance") % tr("Unlocked balance") % tr("Outputs") % tr("Label");
+ std::vector<tools::wallet2::transfer_details> transfers;
+ m_wallet->get_transfers(transfers);
+ for (const auto& i : balance_per_subaddress)
+ {
+ cryptonote::subaddress_index subaddr_index = {m_current_subaddress_account, i.first};
+ std::string address_str = m_wallet->get_subaddress_as_str(subaddr_index).substr(0, 6);
+ uint64_t num_unspent_outputs = std::count_if(transfers.begin(), transfers.end(), [&subaddr_index](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == subaddr_index; });
+ success_msg_writer() << boost::format(tr("%8u %6s %21s %21s %7u %21s")) % i.first % address_str % print_money(i.second) % print_money(unlocked_balance_per_subaddress[i.first]) % num_unspent_outputs % m_wallet->get_subaddress_label(subaddr_index);
+ }
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/)
{
+ if (args.size() > 1 || (args.size() == 1 && args[0] != "detail"))
+ {
+ fail_msg_writer() << tr("usage: balance [detail]");
+ return true;
+ }
LOCK_IDLE_SCOPE();
- show_balance_unlocked();
+ show_balance_unlocked(args.size() == 1);
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args)
{
+ if (args.size() > 3)
+ {
+ fail_msg_writer() << tr("usage: incoming_transfers [available|unavailable] [verbose] [index=<N>]");
+ return true;
+ }
+ auto local_args = args;
LOCK_IDLE_SCOPE();
bool filter = false;
bool available = false;
bool verbose = false;
- for (const auto &arg: args)
+ if (local_args.size() > 0)
{
- if (arg == "available")
+ if (local_args[0] == "available")
{
filter = true;
available = true;
+ local_args.erase(local_args.begin());
}
- else if (arg == "unavailable")
+ else if (local_args[0] == "unavailable")
{
filter = true;
available = false;
+ local_args.erase(local_args.begin());
}
- else if (arg == "verbose")
- {
- verbose = true;
- }
+ }
+ if (local_args.size() > 0 && local_args[0] == "verbose")
+ {
+ verbose = true;
+ local_args.erase(local_args.begin());
}
PAUSE_READLINE();
+ std::set<uint32_t> subaddr_indices;
+ if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=")
+ {
+ if (!parse_subaddress_indices(local_args[0], subaddr_indices))
+ return true;
+ }
+
tools::wallet2::transfer_container transfers;
m_wallet->get_transfers(transfers);
@@ -2035,25 +2188,28 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
{
if (!filter || available != td.m_spent)
{
+ if (m_current_subaddress_account != td.m_subaddr_index.major || (!subaddr_indices.empty() && subaddr_indices.count(td.m_subaddr_index.minor) == 0))
+ continue;
if (!transfers_found)
{
std::string verbose_string;
if (verbose)
verbose_string = (boost::format("%68s%68s") % tr("pubkey") % tr("key image")).str();
- message_writer() << boost::format("%21s%8s%12s%8s%16s%68s%s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id") % verbose_string;
+ message_writer() << boost::format("%21s%8s%12s%8s%16s%68s%16s%s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id") % tr("addr index") % verbose_string;
transfers_found = true;
}
std::string verbose_string;
if (verbose)
verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : std::string('?', 64))).str();
message_writer(td.m_spent ? console_color_magenta : console_color_green, false) <<
- boost::format("%21s%8s%12s%8s%16u%68s%s") %
+ boost::format("%21s%8s%12s%8s%16u%68s%16u%s") %
print_money(td.amount()) %
(td.m_spent ? tr("T") : tr("F")) %
(m_wallet->is_transfer_unlocked(td) ? tr("unlocked") : tr("locked")) %
(td.is_rct() ? tr("RingCT") : tr("-")) %
td.m_global_output_index %
td.m_txid %
+ td.m_subaddr_index.minor %
verbose_string;
}
}
@@ -2089,8 +2245,8 @@ bool simple_wallet::show_payments(const std::vector<std::string> &args)
PAUSE_READLINE();
- message_writer() << boost::format("%68s%68s%12s%21s%16s") %
- tr("payment") % tr("transaction") % tr("height") % tr("amount") % tr("unlock time");
+ message_writer() << boost::format("%68s%68s%12s%21s%16s%16s") %
+ tr("payment") % tr("transaction") % tr("height") % tr("amount") % tr("unlock time") % tr("addr index");
bool payments_found = false;
for(std::string arg : args)
@@ -2113,12 +2269,13 @@ bool simple_wallet::show_payments(const std::vector<std::string> &args)
payments_found = true;
}
success_msg_writer(true) <<
- boost::format("%68s%68s%12s%21s%16s") %
+ boost::format("%68s%68s%12s%21s%16s%16s") %
payment_id %
pd.m_tx_hash %
pd.m_block_height %
print_money(pd.m_amount) %
- pd.m_unlock_time;
+ pd.m_unlock_time %
+ pd.m_subaddr_index.minor;
}
}
else
@@ -2309,6 +2466,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_)
{
+// "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]"
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
if (!try_connect_to_daemon())
return true;
@@ -2317,6 +2475,14 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
std::vector<std::string> local_args = args_;
+ std::set<uint32_t> subaddr_indices;
+ if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=")
+ {
+ if (!parse_subaddress_indices(local_args[0], subaddr_indices))
+ return true;
+ local_args.erase(local_args.begin());
+ }
+
int priority = 0;
if(local_args.size() > 0) {
auto priority_pos = std::find(
@@ -2329,7 +2495,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
}
}
- size_t fake_outs_count;
+ size_t fake_outs_count = 0;
if(local_args.size() > 0) {
size_t ring_size;
if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0]))
@@ -2411,16 +2577,17 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
vector<cryptonote::tx_destination_entry> dsts;
for (size_t i = 0; i < local_args.size(); i += 2)
{
+ cryptonote::address_parse_info info;
cryptonote::tx_destination_entry de;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if (!cryptonote::get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[i], oa_prompter))
+ if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[i], oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
}
+ de.addr = info.address;
+ de.is_subaddress = info.is_subaddress;
- if (has_payment_id)
+ if (info.has_payment_id)
{
if (payment_id_seen)
{
@@ -2429,7 +2596,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
}
std::string extra_nonce;
- set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, new_payment_id);
+ set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id);
bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce);
if(!r)
{
@@ -2480,10 +2647,10 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
return true;
}
unlock_block = bc_height + locked_blocks;
- ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_trusted_daemon);
+ ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon);
break;
case TransferNew:
- ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_trusted_daemon);
+ ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon);
break;
default:
LOG_ERROR("Unknown transfer method, using original");
@@ -2525,7 +2692,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
}
else
{
- if (nblocks[0].first > 0)
+ if (nblocks[0].first > m_wallet->get_confirm_backlog_threshold())
prompt << (boost::format(tr("There is currently a %u block backlog at that fee level. Is this okay? (Y/Yes/N/No)")) % nblocks[0].first).str();
}
}
@@ -2570,6 +2737,17 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
}
std::stringstream prompt;
+ for (size_t n = 0; n < ptx_vector.size(); ++n)
+ {
+ prompt << tr("\nTransaction ") << (n + 1) << "/" << ptx_vector.size() << ":\n";
+ subaddr_indices.clear();
+ for (uint32_t i : ptx_vector[n].construction_data.subaddr_indices)
+ subaddr_indices.insert(i);
+ for (uint32_t i : subaddr_indices)
+ prompt << boost::format(tr("Spending from address index %d\n")) % i;
+ if (subaddr_indices.size() > 1)
+ prompt << tr("WARNING: Outputs of multiple addresses are being used together, which might potentially compromise your privacy.\n");
+ }
prompt << boost::format(tr("Sending %s. ")) % print_money(total_sent);
if (ptx_vector.size() > 1)
{
@@ -2621,15 +2799,9 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx";
}
}
- else while (!ptx_vector.empty())
+ else
{
- auto & ptx = ptx_vector.back();
- m_wallet->commit_tx(ptx);
- success_msg_writer(true) << tr("Transaction successfully submitted, transaction ") << get_transaction_hash(ptx.tx) << ENDL
- << tr("You can check its status by using the `show_transfers` command.");
-
- // if no exception, remove element from vector
- ptx_vector.pop_back();
+ commit_or_save(ptx_vector, m_do_not_relay);
}
}
catch (const tools::error::daemon_busy&)
@@ -2799,14 +2971,9 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx";
}
}
- else while (!ptx_vector.empty())
+ else
{
- auto & ptx = ptx_vector.back();
- m_wallet->commit_tx(ptx);
- success_msg_writer(true) << tr("Money successfully sent, transaction: ") << get_transaction_hash(ptx.tx);
-
- // if no exception, remove element from vector
- ptx_vector.pop_back();
+ commit_or_save(ptx_vector, m_do_not_relay);
}
}
catch (const tools::error::daemon_busy&)
@@ -2900,13 +3067,22 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &args_)
{
+ // sweep_all [index=<N1>[,<N2>,...]] [<ring_size>] <address> [<payment_id>]
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
if (!try_connect_to_daemon())
return true;
std::vector<std::string> local_args = args_;
- size_t fake_outs_count;
+ std::set<uint32_t> subaddr_indices;
+ if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=")
+ {
+ if (!parse_subaddress_indices(local_args[0], subaddr_indices))
+ return true;
+ local_args.erase(local_args.begin());
+ }
+
+ size_t fake_outs_count = 0;
if(local_args.size() > 0) {
size_t ring_size;
if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0]))
@@ -2924,10 +3100,9 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
std::vector<uint8_t> extra;
bool payment_id_seen = false;
- if (2 == local_args.size())
+ if (2 >= local_args.size())
{
std::string payment_id_str = local_args.back();
- local_args.pop_back();
crypto::hash payment_id;
bool r = tools::wallet2::parse_long_payment_id(payment_id_str, payment_id);
@@ -2936,6 +3111,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
std::string extra_nonce;
set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id);
r = add_extra_nonce_to_tx_extra(extra, extra_nonce);
+ payment_id_seen = true;
}
else
{
@@ -2946,15 +3122,17 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
std::string extra_nonce;
set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8);
r = add_extra_nonce_to_tx_extra(extra, extra_nonce);
+ payment_id_seen = true;
}
}
- if(!r)
+ if(!r && local_args.size() == 3)
{
fail_msg_writer() << tr("payment id has invalid format, expected 16 or 64 character hex string: ") << payment_id_str;
return true;
}
- payment_id_seen = true;
+ if (payment_id_seen)
+ local_args.pop_back();
}
if (local_args.size() == 0)
@@ -2963,16 +3141,14 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
return true;
}
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- cryptonote::account_public_address address;
- if (!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[0], oa_prompter))
+ cryptonote::address_parse_info info;
+ if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[0], oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
}
- if (has_payment_id)
+ if (info.has_payment_id)
{
if (payment_id_seen)
{
@@ -2981,7 +3157,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
}
std::string extra_nonce;
- set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, new_payment_id);
+ set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id);
bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce);
if(!r)
{
@@ -3010,7 +3186,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
try
{
// figure out what tx will be necessary
- auto ptx_vector = m_wallet->create_transactions_all(below, address, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon);
+ auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon);
if (ptx_vector.empty())
{
@@ -3028,7 +3204,18 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
}
std::ostringstream prompt;
- if (!print_ring_members(ptx_vector, prompt))
+ for (size_t n = 0; n < ptx_vector.size(); ++n)
+ {
+ prompt << tr("\nTransaction ") << (n + 1) << "/" << ptx_vector.size() << ":\n";
+ subaddr_indices.clear();
+ for (uint32_t i : ptx_vector[n].construction_data.subaddr_indices)
+ subaddr_indices.insert(i);
+ for (uint32_t i : subaddr_indices)
+ prompt << boost::format(tr("Spending from address index %d\n")) % i;
+ if (subaddr_indices.size() > 1)
+ prompt << tr("WARNING: Outputs of multiple addresses are being used together, which might potentially compromise your privacy.\n");
+ }
+ if (m_wallet->print_ring_members() && !print_ring_members(ptx_vector, prompt))
return true;
if (ptx_vector.size() > 1) {
prompt << boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) %
@@ -3064,14 +3251,9 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx";
}
}
- else while (!ptx_vector.empty())
+ else
{
- auto & ptx = ptx_vector.back();
- m_wallet->commit_tx(ptx);
- success_msg_writer(true) << tr("Money successfully sent, transaction: ") << get_transaction_hash(ptx.tx);
-
- // if no exception, remove element from vector
- ptx_vector.pop_back();
+ commit_or_save(ptx_vector, m_do_not_relay);
}
}
catch (const tools::error::daemon_busy&)
@@ -3187,17 +3369,16 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_)
bool simple_wallet::donate(const std::vector<std::string> &args_)
{
std::vector<std::string> local_args = args_;
- if(local_args.empty() || local_args.size() > 3)
+ if(local_args.empty() || local_args.size() > 5)
{
- fail_msg_writer() << tr("wrong number of arguments");
+ fail_msg_writer() << tr("usage: donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]");
return true;
}
- std::string ring_size_str;
// Hardcode Monero's donation address (see #1447)
const std::string address_str = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A";
std::string amount_str;
std::string payment_id_str;
- // check payment id
+ // get payment id and pop
crypto::hash payment_id;
crypto::hash8 payment_id8;
if (tools::wallet2::parse_long_payment_id (local_args.back(), payment_id ) ||
@@ -3206,17 +3387,10 @@ bool simple_wallet::donate(const std::vector<std::string> &args_)
payment_id_str = local_args.back();
local_args.pop_back();
}
- // check ring size
- if (local_args.size() > 1)
- {
- ring_size_str = local_args[0];
- local_args.erase(local_args.begin());
- }
- amount_str = local_args[0];
- // refill args as necessary
- local_args.clear();
- if (!ring_size_str.empty())
- local_args.push_back(ring_size_str);
+ // get amount and pop
+ amount_str = local_args.back();
+ local_args.pop_back();
+ // push back address, amount, payment id
local_args.push_back(address_str);
local_args.push_back(amount_str);
if (!payment_id_str.empty())
@@ -3231,8 +3405,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes,
// gather info to ask the user
uint64_t amount = 0, amount_to_dests = 0, change = 0;
size_t min_ring_size = ~0;
- std::unordered_map<std::string, std::pair<std::string, uint64_t>> dests;
- const std::string wallet_address = m_wallet->get_account().get_public_address_str(m_wallet->testnet());
+ std::unordered_map<cryptonote::account_public_address, std::pair<std::string, uint64_t>> dests;
int first_known_non_zero_change_index = -1;
std::string payment_id_string = "";
for (size_t n = 0; n < get_num_txes(); ++n)
@@ -3241,7 +3414,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes,
std::vector<tx_extra_field> tx_extra_fields;
bool has_encrypted_payment_id = false;
- crypto::hash8 payment_id8 = cryptonote::null_hash8;
+ crypto::hash8 payment_id8 = crypto::null_hash8;
if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields))
{
tx_extra_nonce extra_nonce;
@@ -3274,24 +3447,24 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes,
for (size_t d = 0; d < cd.splitted_dsts.size(); ++d)
{
const tx_destination_entry &entry = cd.splitted_dsts[d];
- std::string address, standard_address = get_account_address_as_str(m_wallet->testnet(), entry.addr);
- if (has_encrypted_payment_id)
+ std::string address, standard_address = get_account_address_as_str(m_wallet->testnet(), entry.is_subaddress, entry.addr);
+ if (has_encrypted_payment_id && !entry.is_subaddress)
{
address = get_account_integrated_address_as_str(m_wallet->testnet(), entry.addr, payment_id8);
address += std::string(" (" + standard_address + " with encrypted payment id " + epee::string_tools::pod_to_hex(payment_id8) + ")");
}
else
address = standard_address;
- std::unordered_map<std::string,std::pair<std::string,uint64_t>>::iterator i = dests.find(standard_address);
+ auto i = dests.find(entry.addr);
if (i == dests.end())
- dests.insert(std::make_pair(standard_address, std::make_pair(address, entry.amount)));
+ dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount)));
else
i->second.second += entry.amount;
amount_to_dests += entry.amount;
}
if (cd.change_dts.amount > 0)
{
- std::unordered_map<std::string, std::pair<std::string, uint64_t>>::iterator it = dests.find(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr));
+ auto it = dests.find(cd.change_dts.addr);
if (it == dests.end())
{
fail_msg_writer() << tr("Claimed change does not go to a paid address");
@@ -3315,7 +3488,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes,
change += cd.change_dts.amount;
it->second.second -= cd.change_dts.amount;
if (it->second.second == 0)
- dests.erase(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr));
+ dests.erase(cd.change_dts.addr);
}
}
@@ -3323,7 +3496,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes,
payment_id_string = "no payment ID";
std::string dest_string;
- for (std::unordered_map<std::string, std::pair<std::string, uint64_t>>::const_iterator i = dests.begin(); i != dests.end(); )
+ for (auto i = dests.begin(); i != dests.end(); )
{
dest_string += (boost::format(tr("sending %s to %s")) % print_money(i->second.second) % i->second.first).str();
++i;
@@ -3336,7 +3509,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes,
std::string change_string;
if (change > 0)
{
- std::string address = get_account_address_as_str(m_wallet->testnet(), get_tx(0).change_dts.addr);
+ std::string address = get_account_address_as_str(m_wallet->testnet(), get_tx(0).subaddr_account > 0, get_tx(0).change_dts.addr);
change_string += (boost::format(tr("%s change to %s")) % print_money(change) % address).str();
}
else
@@ -3370,12 +3543,18 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_)
fail_msg_writer() << tr("This is a watch only wallet");
return true;
}
+ if (args_.size() > 1 || (args_.size() == 1 && args_[0] != "export"))
+ {
+ fail_msg_writer() << tr("usage: sign_transfer [export]");
+ return true;
+ }
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+ const bool export_raw = args_.size() == 1;
std::vector<tools::wallet2::pending_tx> ptx;
try
{
- bool r = m_wallet->sign_tx("unsigned_monero_tx", "signed_monero_tx", ptx, [&](const tools::wallet2::unsigned_tx_set &tx){ return accept_loaded_tx(tx); });
+ bool r = m_wallet->sign_tx("unsigned_monero_tx", "signed_monero_tx", ptx, [&](const tools::wallet2::unsigned_tx_set &tx){ return accept_loaded_tx(tx); }, export_raw);
if (!r)
{
fail_msg_writer() << tr("Failed to sign transaction");
@@ -3396,6 +3575,17 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_)
txids_as_text += epee::string_tools::pod_to_hex(get_transaction_hash(t.tx));
}
success_msg_writer(true) << tr("Transaction successfully signed to file ") << "signed_monero_tx" << ", txid " << txids_as_text;
+ if (export_raw)
+ {
+ std::string rawfiles_as_text;
+ for (size_t i = 0; i < ptx.size(); ++i)
+ {
+ if (i > 0)
+ rawfiles_as_text += ", ";
+ rawfiles_as_text += "signed_monero_tx_raw" + (ptx.size() == 1 ? "" : ("_" + std::to_string(i)));
+ }
+ success_msg_writer(true) << tr("Transaction raw hex data exported to ") << rawfiles_as_text;
+ }
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -3414,16 +3604,7 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_)
return true;
}
- // actually commit the transactions
- while (!ptx_vector.empty())
- {
- auto & ptx = ptx_vector.back();
- m_wallet->commit_tx(ptx);
- success_msg_writer(true) << tr("Money successfully sent, transaction: ") << get_transaction_hash(ptx.tx);
-
- // if no exception, remove element from vector
- ptx_vector.pop_back();
- }
+ commit_or_save(ptx_vector, false);
}
catch (const tools::error::daemon_busy&)
{
@@ -3474,6 +3655,9 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_)
catch (const tools::error::tx_rejected& e)
{
fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status();
+ std::string reason = e.reason();
+ if (!reason.empty())
+ fail_msg_writer() << tr("Reason: ") << reason;
}
catch (const tools::error::tx_sum_overflow& e)
{
@@ -3532,8 +3716,9 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
LOCK_IDLE_SCOPE();
crypto::secret_key tx_key;
+ std::vector<crypto::secret_key> additional_tx_keys;
std::vector<crypto::secret_key> amount_keys;
- if (m_wallet->get_tx_key(txid, tx_key))
+ if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys))
{
success_msg_writer() << tr("Tx key: ") << epee::string_tools::pod_to_hex(tx_key);
return true;
@@ -3547,8 +3732,8 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
{
- if(args.size() != 2 && args.size() != 3) {
- fail_msg_writer() << tr("usage: get_tx_proof <txid> <dest_address> [<tx_key>]");
+ if(args.size() != 2) {
+ fail_msg_writer() << tr("usage: get_tx_proof <txid> <dest_address>");
return true;
}
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
@@ -3561,10 +3746,8 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
}
crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 payment_id;
- if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), args[1], oa_prompter))
+ cryptonote::address_parse_info info;
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[1], oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -3572,43 +3755,126 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
LOCK_IDLE_SCOPE();
- crypto::secret_key tx_key, tx_key2;
- bool r = m_wallet->get_tx_key(txid, tx_key);
- cryptonote::blobdata tx_key_data;
- if (args.size() == 3)
+ crypto::secret_key tx_key;
+ std::vector<crypto::secret_key> additional_tx_keys;
+ if (!m_wallet->get_tx_key(txid, tx_key, additional_tx_keys))
{
- if(!epee::string_tools::parse_hexstr_to_binbuff(args[2], tx_key_data) || tx_key_data.size() != sizeof(crypto::secret_key))
+ fail_msg_writer() << tr("Tx secret key wasn't found in the wallet file.");
+ return true;
+ }
+
+ // fetch tx prefix either from the daemon or from the wallet cache if it's still pending
+ cryptonote::transaction_prefix tx;
+ // first, look up the wallet cache
+ std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> unconfirmed_payments_out;
+ m_wallet->get_unconfirmed_payments_out(unconfirmed_payments_out, m_current_subaddress_account);
+ auto found = std::find_if(unconfirmed_payments_out.begin(), unconfirmed_payments_out.end(), [&](const std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>& p)
+ {
+ return p.first == txid;
+ });
+ // if not found, query the daemon
+ if (found == unconfirmed_payments_out.end())
+ {
+ COMMAND_RPC_GET_TRANSACTIONS::request req;
+ COMMAND_RPC_GET_TRANSACTIONS::response res;
+ req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
+ if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) ||
+ (res.txs.size() != 1 && res.txs_as_hex.size() != 1))
{
- fail_msg_writer() << tr("failed to parse tx_key");
+ fail_msg_writer() << tr("failed to get transaction from daemon");
return true;
}
- tx_key2 = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data());
- }
- if (r)
- {
- if (args.size() == 3 && tx_key != rct::sk2rct(tx_key2))
+ cryptonote::blobdata tx_data;
+ bool ok;
+ if (res.txs.size() == 1)
+ ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data);
+ else
+ ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
+ if (!ok)
+ {
+ fail_msg_writer() << tr("failed to parse transaction from daemon");
+ return true;
+ }
+ crypto::hash tx_hash, tx_prefix_hash;
+ cryptonote::transaction tx_full;
+ if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx_full, tx_hash, tx_prefix_hash))
{
- fail_msg_writer() << tr("Tx secret key was found for the given txid, but you've also provided another tx secret key which doesn't match the found one.");
+ fail_msg_writer() << tr("failed to validate transaction from daemon");
return true;
}
+ if (tx_hash != txid)
+ {
+ fail_msg_writer() << tr("failed to get the right transaction from daemon");
+ return true;
+ }
+ tx = tx_full;
+ }
+ else
+ {
+ tx = found->second.m_tx;
+ }
+
+ // fetch tx pubkey
+ crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
+ if (tx_pub_key == null_pkey)
+ {
+ fail_msg_writer() << tr("Tx pubkey was not found");
+ return true;
+ }
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
+ if (additional_tx_keys.size() != additional_tx_pub_keys.size())
+ {
+ fail_msg_writer() << tr("The set of additional tx secret/public keys doesn't match");
+ return true;
+ }
+
+ // find the correct R:
+ // R = r*G for standard addresses
+ // R = r*B for subaddresses (where B is the recipient's spend pubkey)
+ crypto::public_key R;
+ crypto::secret_key r;
+ if (info.is_subaddress)
+ {
+ auto i = additional_tx_keys.begin();
+ auto j = additional_tx_pub_keys.begin();
+ for (; ; ++i, ++j) {
+ if (i == additional_tx_keys.end())
+ {
+ r = tx_key;
+ R = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_spend_public_key), rct::sk2rct(r)));
+ if (R == tx_pub_key)
+ break;
+ fail_msg_writer() << tr("The matching tx secret/pubkey pair wasn't found for this subaddress");
+ return true;
+ }
+ else
+ {
+ r = *i;
+ R = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_spend_public_key), rct::sk2rct(r)));
+ if (R == *j)
+ break;
+ }
+ }
}
else
{
- if (tx_key_data.empty())
+ r = tx_key;
+ crypto::secret_key_to_public_key(r, R);
+ if (R != tx_pub_key)
{
- fail_msg_writer() << tr("Tx secret key wasn't found in the wallet file. Provide it as the optional third parameter if you have it elsewhere.");
+ fail_msg_writer() << tr("The destinations of this tx don't include a standard address");
return true;
}
- tx_key = tx_key2;
}
- crypto::public_key R;
- crypto::secret_key_to_public_key(tx_key, R);
- crypto::public_key rA = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key)));
+ crypto::public_key rA = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_view_public_key), rct::sk2rct(r)));
crypto::signature sig;
try
{
- crypto::generate_tx_proof(txid, R, address.m_view_public_key, rA, tx_key, sig);
+ if (info.is_subaddress)
+ crypto::generate_tx_proof(txid, R, info.address.m_view_public_key, info.address.m_spend_public_key, rA, r, sig);
+ else
+ crypto::generate_tx_proof(txid, R, info.address.m_view_public_key, boost::none, rA, r, sig);
}
catch (const std::runtime_error &e)
{
@@ -3663,26 +3929,24 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_)
}
tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data());
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 payment_id;
- if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), local_args[2], oa_prompter))
+ cryptonote::address_parse_info info;
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[2], oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
}
crypto::key_derivation derivation;
- if (!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation))
+ if (!crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation))
{
fail_msg_writer() << tr("failed to generate key derivation from supplied parameters");
return true;
}
- return check_tx_key_helper(txid, address, derivation);
+ return check_tx_key_helper(txid, info.address, info.is_subaddress, derivation);
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, const crypto::key_derivation &derivation)
+bool simple_wallet::check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const crypto::key_derivation &derivation)
{
COMMAND_RPC_GET_TRANSACTIONS::request req;
COMMAND_RPC_GET_TRANSACTIONS::response res;
@@ -3765,13 +4029,14 @@ bool simple_wallet::check_tx_key_helper(const crypto::hash &txid, const cryptono
return true;
}
+ std::string address_str = get_account_address_as_str(m_wallet->testnet(), is_subaddress, address);
if (received > 0)
{
- success_msg_writer() << get_account_address_as_str(m_wallet->testnet(), address) << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid;
+ success_msg_writer() << address_str << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid;
}
else
{
- fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), address) << " " << tr("received nothing in txid") << " " << txid;
+ fail_msg_writer() << address_str << " " << tr("received nothing in txid") << " " << txid;
}
if (res.txs.front().in_pool)
{
@@ -3815,10 +4080,8 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args)
crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
// parse address
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 payment_id;
- if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), args[1], oa_prompter))
+ cryptonote::address_parse_info info;
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[1], oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -3889,15 +4152,34 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args)
fail_msg_writer() << tr("failed to get the right transaction from daemon");
return true;
}
- crypto::public_key R = get_tx_pub_key_from_extra(tx);
- if (R == null_pkey)
+ crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
+ if (tx_pub_key == null_pkey)
{
fail_msg_writer() << tr("Tx pubkey was not found");
return true;
}
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
// check signature
- if (crypto::check_tx_proof(txid, R, address.m_view_public_key, rA, sig))
+ bool good_signature = false;
+ if (info.is_subaddress)
+ {
+ good_signature = crypto::check_tx_proof(txid, tx_pub_key, info.address.m_view_public_key, info.address.m_spend_public_key, rA, sig);
+ if (!good_signature)
+ {
+ for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
+ {
+ good_signature = crypto::check_tx_proof(txid, additional_tx_pub_keys[i], info.address.m_view_public_key, info.address.m_spend_public_key, rA, sig);
+ if (good_signature)
+ break;
+ }
+ }
+ }
+ else
+ {
+ good_signature = crypto::check_tx_proof(txid, tx_pub_key, info.address.m_view_public_key, boost::none, rA, sig);
+ }
+ if (good_signature)
{
success_msg_writer() << tr("Good signature");
}
@@ -3915,7 +4197,7 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args)
return true;
}
- return check_tx_key_helper(txid, address, derivation);
+ return check_tx_key_helper(txid, info.address, info.is_subaddress, derivation);
}
//----------------------------------------------------------------------------------------------------
static std::string get_human_readable_timestamp(uint64_t ts)
@@ -3965,9 +4247,10 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
bool pool = true;
uint64_t min_height = 0;
uint64_t max_height = (uint64_t)-1;
+ boost::optional<uint32_t> subaddr_index;
- if(local_args.size() > 3) {
- fail_msg_writer() << tr("usage: show_transfers [in|out|all|pending|failed] [<min_height> [<max_height>]]");
+ if(local_args.size() > 4) {
+ fail_msg_writer() << tr("usage: show_transfers [in|out|all|pending|failed] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]");
return true;
}
@@ -4000,6 +4283,15 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
}
}
+ // subaddr_index
+ std::set<uint32_t> subaddr_indices;
+ if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=")
+ {
+ if (!parse_subaddress_indices(local_args[0], subaddr_indices))
+ return true;
+ local_args.erase(local_args.begin());
+ }
+
// min height
if (local_args.size() > 0) {
try {
@@ -4030,20 +4322,32 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
if (in) {
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
- m_wallet->get_payments(payments, min_height, max_height);
+ m_wallet->get_payments(payments, min_height, max_height, m_current_subaddress_account, subaddr_indices);
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
const tools::wallet2::payment_details &pd = i->second;
std::string payment_id = string_tools::pod_to_hex(i->first);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
std::string note = m_wallet->get_tx_note(pd.m_tx_hash);
- output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%16.16s %20.20s %s %s %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % "-" % note).str())));
+ output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%16.16s %20.20s %s %s %d %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str())));
}
}
+ auto print_subaddr_indices = [](const std::set<uint32_t>& indices)
+ {
+ stringstream ss;
+ bool first = true;
+ for (uint32_t i : indices)
+ {
+ ss << (first ? "" : ",") << i;
+ first = false;
+ }
+ return ss.str();
+ };
+
if (out) {
std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments;
- m_wallet->get_payments_out(payments, min_height, max_height);
+ m_wallet->get_payments_out(payments, min_height, max_height, m_current_subaddress_account, subaddr_indices);
for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
const tools::wallet2::confirmed_transfer_details &pd = i->second;
uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known
@@ -4052,13 +4356,13 @@ bool simple_wallet::show_transfers(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->testnet(), d.addr) + ": " + print_money(d.amount);
+ dests += get_account_address_as_str(m_wallet->testnet(), d.is_subaddress, d.addr) + ": " + 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)
payment_id = payment_id.substr(0,16);
std::string note = m_wallet->get_tx_note(i->first);
- output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%16.16s %20.20s %s %s %14.14s %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests % note).str())));
+ output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%16.16s %20.20s %s %s %14.14s %s %s - %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests % print_subaddr_indices(pd.m_subaddr_indices) % note).str())));
}
}
@@ -4074,26 +4378,26 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
{
m_wallet->update_pool_state();
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
- m_wallet->get_unconfirmed_payments(payments);
+ m_wallet->get_unconfirmed_payments(payments, m_current_subaddress_account, subaddr_indices);
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
const tools::wallet2::payment_details &pd = i->second;
std::string payment_id = string_tools::pod_to_hex(i->first);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
std::string note = m_wallet->get_tx_note(pd.m_tx_hash);
- message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %s %s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % "-" % note).str();
+ message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %d %s %s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str();
}
}
- catch (...)
+ catch (const std::exception& e)
{
- fail_msg_writer() << "Failed to get pool state";
+ fail_msg_writer() << "Failed to get pool state:" << e.what();
}
}
// print unconfirmed last
if (pending || failed) {
std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments;
- m_wallet->get_unconfirmed_payments_out(upayments);
+ m_wallet->get_unconfirmed_payments_out(upayments, m_current_subaddress_account, subaddr_indices);
for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
const tools::wallet2::unconfirmed_transfer_details &pd = i->second;
uint64_t amount = pd.m_amount_in;
@@ -4104,7 +4408,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
std::string note = m_wallet->get_tx_note(i->first);
bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed;
if ((failed && is_failed) || (!is_failed && pending)) {
- message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %14.14s %s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % get_human_readable_timestamp(pd.m_timestamp) % print_money(amount - pd.m_change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % note).str();
+ message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %14.14s %s - %s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % get_human_readable_timestamp(pd.m_timestamp) % print_money(amount - pd.m_change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % print_subaddr_indices(pd.m_subaddr_indices) % note).str();
}
}
}
@@ -4114,20 +4418,40 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
{
- if(!args_.empty() && args_.size() != 2) {
- fail_msg_writer() << tr("usage: unspent_outputs [<min_amount> <max_amount>]");
+ if(args_.size() > 3)
+ {
+ fail_msg_writer() << tr("usage: unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]");
return true;
}
+ auto local_args = args_;
+
+ std::set<uint32_t> subaddr_indices;
+ if (local_args.size() > 0 && local_args[0].substr(0, 6) != "index=")
+ {
+ if (!parse_subaddress_indices(local_args[0], subaddr_indices))
+ return true;
+ local_args.erase(local_args.begin());
+ }
+
uint64_t min_amount = 0;
uint64_t max_amount = std::numeric_limits<uint64_t>::max();
- if (args_.size() == 2)
+ if (local_args.size() > 0)
{
- if (!cryptonote::parse_amount(min_amount, args_[0]) || !cryptonote::parse_amount(max_amount, args_[1]))
+ if (!cryptonote::parse_amount(min_amount, local_args[0]))
{
- fail_msg_writer() << tr("amount is wrong: ") << args_[0] << ' ' << args_[1] <<
- ", " << tr("expected number from 0 to ") << print_money(std::numeric_limits<uint64_t>::max());
+ fail_msg_writer() << tr("amount is wrong: ") << local_args[0];
return true;
}
+ local_args.erase(local_args.begin());
+ if (local_args.size() > 0)
+ {
+ if (!cryptonote::parse_amount(max_amount, local_args[0]))
+ {
+ fail_msg_writer() << tr("amount is wrong: ") << local_args[0];
+ return true;
+ }
+ local_args.erase(local_args.begin());
+ }
if (min_amount > max_amount)
{
fail_msg_writer() << tr("<min_amount> should be smaller than <max_amount>");
@@ -4136,11 +4460,6 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
}
tools::wallet2::transfer_container transfers;
m_wallet->get_transfers(transfers);
- if (transfers.empty())
- {
- success_msg_writer() << "There is no unspent output in this wallet.";
- return true;
- }
std::map<uint64_t, tools::wallet2::transfer_container> amount_to_tds;
uint64_t min_height = std::numeric_limits<uint64_t>::max();
uint64_t max_height = 0;
@@ -4150,7 +4469,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
for (const auto& td : transfers)
{
uint64_t amount = td.amount();
- if (td.m_spent || amount < min_amount || amount > max_amount)
+ if (td.m_spent || amount < min_amount || amount > max_amount || td.m_subaddr_index.major != m_current_subaddress_account || subaddr_indices.count(td.m_subaddr_index.minor) == 0)
continue;
amount_to_tds[amount].push_back(td);
if (min_height > td.m_block_height) min_height = td.m_block_height;
@@ -4159,6 +4478,11 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
if (found_max_amount < amount) found_max_amount = amount;
++count;
}
+ if (amount_to_tds.empty())
+ {
+ success_msg_writer() << tr("There is no unspent output in the specified address");
+ return true;
+ }
for (const auto& amount_tds : amount_to_tds)
{
auto& tds = amount_tds.second;
@@ -4265,7 +4589,7 @@ void simple_wallet::wallet_idle_thread()
//----------------------------------------------------------------------------------------------------
std::string simple_wallet::get_prompt() const
{
- std::string addr_start = m_wallet->get_account().get_public_address_str(m_wallet->testnet()).substr(0, 6);
+ 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;
uint32_t version;
if (!m_wallet->check_connection(&version))
@@ -4281,7 +4605,7 @@ bool simple_wallet::run()
// check and display warning, but go on anyway
try_connect_to_daemon();
- refresh_main(0, false);
+ refresh_main(0, false, true);
m_auto_refresh_enabled = m_wallet->auto_refresh();
m_idle_thread = boost::thread([&]{wallet_idle_thread();});
@@ -4301,9 +4625,197 @@ void simple_wallet::stop()
m_idle_cond.notify_one();
}
//----------------------------------------------------------------------------------------------------
+bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ // Usage:
+ // account
+ // account new <label text with white spaces allowed>
+ // account switch <index>
+ // account label <index> <label text with white spaces allowed>
+
+ if (args.empty())
+ {
+ // print all the existing accounts
+ LOCK_IDLE_SCOPE();
+ print_accounts();
+ return true;
+ }
+
+ std::vector<std::string> local_args = args;
+ std::string command = local_args[0];
+ local_args.erase(local_args.begin());
+ if (command == "new")
+ {
+ // create a new account and switch to it
+ std::string label = boost::join(local_args, " ");
+ if (label.empty())
+ label = tr("(Untitled account)");
+ m_wallet->add_subaddress_account(label);
+ m_current_subaddress_account = m_wallet->get_num_subaddress_accounts() - 1;
+ // update_prompt();
+ LOCK_IDLE_SCOPE();
+ print_accounts();
+ }
+ else if (command == "switch" && local_args.size() == 1)
+ {
+ // switch to the specified account
+ uint32_t index_major;
+ if (!epee::string_tools::get_xtype_from_string(index_major, local_args[0]))
+ {
+ fail_msg_writer() << tr("failed to parse index: ") << local_args[0];
+ return true;
+ }
+ if (index_major >= m_wallet->get_num_subaddress_accounts())
+ {
+ fail_msg_writer() << tr("specify an index between 0 and ") << (m_wallet->get_num_subaddress_accounts() - 1);
+ return true;
+ }
+ m_current_subaddress_account = index_major;
+ // update_prompt();
+ show_balance();
+ }
+ else if (command == "label" && local_args.size() >= 1)
+ {
+ // set label of the specified account
+ uint32_t index_major;
+ if (!epee::string_tools::get_xtype_from_string(index_major, local_args[0]))
+ {
+ fail_msg_writer() << tr("failed to parse index: ") << local_args[0];
+ return true;
+ }
+ local_args.erase(local_args.begin());
+ std::string label = boost::join(local_args, " ");
+ try
+ {
+ m_wallet->set_subaddress_label({index_major, 0}, label);
+ LOCK_IDLE_SCOPE();
+ print_accounts();
+ }
+ catch (const std::exception& e)
+ {
+ fail_msg_writer() << e.what();
+ }
+ }
+ else
+ {
+ fail_msg_writer() << tr("usage: account [new <label text with white spaces allowed> | switch <index> | label <index> <label text with white spaces allowed>]");
+ }
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+void simple_wallet::print_accounts()
+{
+ success_msg_writer() << boost::format("%15s %21s %21s %21s") % tr("Account") % tr("Balance") % tr("Unlocked balance") % tr("Label");
+ for (uint32_t account_index = 0; account_index < m_wallet->get_num_subaddress_accounts(); ++account_index)
+ {
+ success_msg_writer() << boost::format(tr("%8u %6s %21s %21s %21s"))
+ % account_index
+ % m_wallet->get_subaddress_as_str({account_index, 0}).substr(0, 6)
+ % print_money(m_wallet->balance(account_index))
+ % print_money(m_wallet->unlocked_balance(account_index))
+ % m_wallet->get_subaddress_label({account_index, 0});
+ }
+ success_msg_writer() << tr("----------------------------------------------------------------------------------");
+ success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(m_wallet->balance_all()) % print_money(m_wallet->unlocked_balance_all());
+}
+//----------------------------------------------------------------------------------------------------
bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
- success_msg_writer() << m_wallet->get_account().get_public_address_str(m_wallet->testnet());
+ // Usage:
+ // address
+ // address new <label text with white spaces allowed>
+ // address all
+ // address <index_min> [<index_max>]
+ // address label <index> <label text with white spaces allowed>
+
+ std::vector<std::string> local_args = args;
+ tools::wallet2::transfer_container transfers;
+ m_wallet->get_transfers(transfers);
+
+ auto print_address_sub = [this, &transfers](uint32_t index)
+ {
+ bool used = std::find_if(
+ transfers.begin(), transfers.end(),
+ [this, &index](const tools::wallet2::transfer_details& td) {
+ return td.m_subaddr_index == cryptonote::subaddress_index{ m_current_subaddress_account, index };
+ }) != transfers.end();
+ success_msg_writer() << index << " " << m_wallet->get_subaddress_as_str({m_current_subaddress_account, index}) << " " << (index == 0 ? tr("Primary address") : m_wallet->get_subaddress_label({m_current_subaddress_account, index})) << " " << (used ? tr("(used)") : "");
+ };
+
+ uint32_t index = 0;
+ if (local_args.empty())
+ {
+ print_address_sub(index);
+ }
+ else if (local_args.size() == 1 && local_args[0] == "all")
+ {
+ local_args.erase(local_args.begin());
+ for (; index < m_wallet->get_num_subaddresses(m_current_subaddress_account); ++index)
+ print_address_sub(index);
+ }
+ else if (local_args[0] == "new")
+ {
+ local_args.erase(local_args.begin());
+ std::string label;
+ if (local_args.size() > 0)
+ label = boost::join(local_args, " ");
+ if (label.empty())
+ label = tr("(Untitled address)");
+ m_wallet->add_subaddress(m_current_subaddress_account, label);
+ print_address_sub(m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1);
+ }
+ else if (local_args.size() >= 2 && local_args[0] == "label")
+ {
+ if (!epee::string_tools::get_xtype_from_string(index, local_args[1]))
+ {
+ fail_msg_writer() << tr("failed to parse index: ") << local_args[1];
+ return true;
+ }
+ if (index >= m_wallet->get_num_subaddresses(m_current_subaddress_account))
+ {
+ fail_msg_writer() << tr("specify an index between 0 and ") << (m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1);
+ return true;
+ }
+ local_args.erase(local_args.begin());
+ local_args.erase(local_args.begin());
+ std::string label = boost::join(local_args, " ");
+ m_wallet->set_subaddress_label({m_current_subaddress_account, index}, label);
+ print_address_sub(index);
+ }
+ else if (local_args.size() <= 2 && epee::string_tools::get_xtype_from_string(index, local_args[0]))
+ {
+ local_args.erase(local_args.begin());
+ uint32_t index_min = index;
+ uint32_t index_max = index_min;
+ if (local_args.size() > 0)
+ {
+ if (!epee::string_tools::get_xtype_from_string(index_max, local_args[0]))
+ {
+ fail_msg_writer() << tr("failed to parse index: ") << local_args[0];
+ return true;
+ }
+ local_args.erase(local_args.begin());
+ }
+ if (index_max < index_min)
+ std::swap(index_min, index_max);
+ if (index_min >= m_wallet->get_num_subaddresses(m_current_subaddress_account))
+ {
+ fail_msg_writer() << tr("<index_min> is already out of bound");
+ return true;
+ }
+ if (index_max >= m_wallet->get_num_subaddresses(m_current_subaddress_account))
+ {
+ message_writer() << tr("<index_max> exceeds the bound");
+ index_max = m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1;
+ }
+ for (index = index_min; index <= index_max; ++index)
+ print_address_sub(index);
+ }
+ else
+ {
+ fail_msg_writer() << tr("usage: address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> ]");
+ }
+
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -4328,19 +4840,17 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg
return true;
}
else {
- bool has_payment_id;
- crypto::hash8 payment_id;
- account_public_address addr;
- if(get_account_integrated_address_from_str(addr, has_payment_id, payment_id, m_wallet->testnet(), args.back()))
+ address_parse_info info;
+ if(get_account_address_from_str(info, m_wallet->testnet(), args.back()))
{
- if (has_payment_id)
+ if (info.has_payment_id)
{
- success_msg_writer() << boost::format(tr("Integrated address: account %s, payment ID %s")) %
- get_account_address_as_str(m_wallet->testnet(),addr) % epee::string_tools::pod_to_hex(payment_id);
+ success_msg_writer() << boost::format(tr("Integrated address: %s, payment ID: %s")) %
+ get_account_address_as_str(m_wallet->testnet(), false, info.address) % epee::string_tools::pod_to_hex(info.payment_id);
}
else
{
- success_msg_writer() << tr("Standard address: ") << get_account_address_as_str(m_wallet->testnet(),addr);
+ success_msg_writer() << (info.is_subaddress ? tr("Subaddress: ") : tr("Standard address: ")) << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address);
}
return true;
}
@@ -4361,29 +4871,27 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v
}
else if (args[0] == "add")
{
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 payment_id8;
- if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), args[1], oa_prompter))
+ cryptonote::address_parse_info info;
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[1], oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
}
crypto::hash payment_id = null_hash;
size_t description_start = 2;
- if (has_payment_id)
+ if (info.has_payment_id)
{
- memcpy(payment_id.data, payment_id8.data, 8);
+ memcpy(payment_id.data, info.payment_id.data, 8);
}
- else if (!has_payment_id && args.size() >= 4 && args[2] == "pid")
+ else if (!info.has_payment_id && args.size() >= 4 && args[2] == "pid")
{
if (tools::wallet2::parse_long_payment_id(args[3], payment_id))
{
description_start += 2;
}
- else if (tools::wallet2::parse_short_payment_id(args[3], payment_id8))
+ else if (tools::wallet2::parse_short_payment_id(args[3], info.payment_id))
{
- memcpy(payment_id.data, payment_id8.data, 8);
+ memcpy(payment_id.data, info.payment_id.data, 8);
description_start += 2;
}
else
@@ -4399,7 +4907,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v
description += " ";
description += args[i];
}
- m_wallet->add_address_book_row(address, payment_id, description);
+ m_wallet->add_address_book_row(info.address, payment_id, description, info.is_subaddress);
}
else
{
@@ -4421,7 +4929,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v
for (size_t i = 0; i < address_book.size(); ++i) {
auto& row = address_book[i];
success_msg_writer() << tr("Index: ") << i;
- success_msg_writer() << tr("Address: ") << get_account_address_as_str(m_wallet->testnet(), row.m_address);
+ success_msg_writer() << tr("Address: ") << get_account_address_as_str(m_wallet->testnet(), row.m_is_subaddress, row.m_address);
success_msg_writer() << tr("Payment ID: ") << row.m_payment_id;
success_msg_writer() << tr("Description: ") << row.m_description << "\n";
}
@@ -4507,6 +5015,15 @@ bool simple_wallet::status(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
+bool simple_wallet::wallet_info(const std::vector<std::string> &args)
+{
+ message_writer() << tr("Filename: ") << m_wallet->get_wallet_file();
+ message_writer() << tr("Address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet());
+ message_writer() << tr("Watch only: ") << (m_wallet->watch_only() ? tr("Yes") : tr("No"));
+ message_writer() << tr("Testnet: ") << (m_wallet->testnet() ? tr("Yes") : tr("No"));
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
bool simple_wallet::sign(const std::vector<std::string> &args)
{
if (args.size() != 1)
@@ -4552,16 +5069,14 @@ bool simple_wallet::verify(const std::vector<std::string> &args)
return true;
}
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 payment_id;
- if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), address_string, oa_prompter))
+ cryptonote::address_parse_info info;
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), address_string, oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
}
- r = m_wallet->verify(data, address, signature);
+ r = m_wallet->verify(data, info.address, signature);
if (!r)
{
fail_msg_writer() << tr("Bad signature from ") << address_string;
@@ -4782,7 +5297,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args)
const uint64_t last_block_height = m_wallet->get_blockchain_current_height();
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
- m_wallet->get_payments(payments, 0);
+ m_wallet->get_payments(payments, 0, m_current_subaddress_account);
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
const tools::wallet2::payment_details &pd = i->second;
if (pd.m_tx_hash == txid) {
@@ -4812,13 +5327,14 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args)
else
success_msg_writer() << "locked for " << get_human_readable_timespan(std::chrono::seconds(pd.m_unlock_time - threshold));
}
+ success_msg_writer() << "Address index: " << pd.m_subaddr_index.minor;
success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid);
return true;
}
}
std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments_out;
- m_wallet->get_payments_out(payments_out, 0);
+ m_wallet->get_payments_out(payments_out, 0, (uint64_t)-1, m_current_subaddress_account);
for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments_out.begin(); i != payments_out.end(); ++i) {
if (i->first == txid)
{
@@ -4829,7 +5345,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->testnet(), d.addr) + ": " + print_money(d.amount);
+ dests += get_account_address_as_str(m_wallet->testnet(), d.is_subaddress, d.addr) + ": " + 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)
@@ -4865,6 +5381,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args)
success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp);
success_msg_writer() << "Amount: " << print_money(pd.m_amount);
success_msg_writer() << "Payment ID: " << payment_id;
+ success_msg_writer() << "Address index: " << pd.m_subaddr_index.minor;
success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid);
return true;
}
@@ -4921,6 +5438,35 @@ void simple_wallet::interrupt()
}
}
//----------------------------------------------------------------------------------------------------
+void simple_wallet::commit_or_save(std::vector<tools::wallet2::pending_tx>& ptx_vector, bool do_not_relay)
+{
+ size_t i = 0;
+ while (!ptx_vector.empty())
+ {
+ auto & ptx = ptx_vector.back();
+ const crypto::hash txid = get_transaction_hash(ptx.tx);
+ if (do_not_relay)
+ {
+ cryptonote::blobdata blob;
+ 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))
+ 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;
+ }
+ else
+ {
+ m_wallet->commit_tx(ptx);
+ success_msg_writer(true) << tr("Transaction successfully submitted, transaction ") << txid << ENDL
+ << tr("You can check its status by using the `show_transfers` command.");
+ }
+ // if no exception, remove element from vector
+ ptx_vector.pop_back();
+ }
+}
+//----------------------------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
po::options_description desc_params(wallet_args::tr("Wallet options"));
@@ -4940,6 +5486,7 @@ int main(int argc, char* argv[])
command_line::add_arg(desc_params, arg_trusted_daemon);
command_line::add_arg(desc_params, arg_allow_mismatched_daemon_version);
command_line::add_arg(desc_params, arg_restore_height);
+ command_line::add_arg(desc_params, arg_do_not_relay);
po::positional_options_description positional_options;
positional_options.add(arg_command.name, -1);
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 079fae9f5..6edd41c32 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -97,6 +97,7 @@ namespace cryptonote
bool viewkey(const std::vector<std::string> &args = std::vector<std::string>());
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>());
/*!
* \brief Sets seed language.
@@ -121,12 +122,14 @@ namespace cryptonote
bool set_min_output_value(const std::vector<std::string> &args = std::vector<std::string>());
bool set_merge_destinations(const std::vector<std::string> &args = std::vector<std::string>());
bool set_confirm_backlog(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_confirm_backlog_threshold(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_refresh_from_block_height(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);
bool save_bc(const std::vector<std::string>& args);
bool refresh(const std::vector<std::string> &args);
- bool show_balance_unlocked();
+ bool show_balance_unlocked(bool detailed = false);
bool show_balance(const std::vector<std::string> &args = std::vector<std::string>());
bool show_incoming_transfers(const std::vector<std::string> &args);
bool show_payments(const std::vector<std::string> &args);
@@ -145,6 +148,8 @@ namespace cryptonote
std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts(
std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits
);
+ bool account(const std::vector<std::string> &args = std::vector<std::string>());
+ void print_accounts();
bool print_address(const std::vector<std::string> &args = std::vector<std::string>());
bool print_integrated_address(const std::vector<std::string> &args = std::vector<std::string>());
bool address_book(const std::vector<std::string> &args = std::vector<std::string>());
@@ -155,16 +160,17 @@ namespace cryptonote
bool set_log(const std::vector<std::string> &args);
bool get_tx_key(const std::vector<std::string> &args);
bool check_tx_key(const std::vector<std::string> &args);
- bool check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, const crypto::key_derivation &derivation);
+ bool check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const crypto::key_derivation &derivation);
bool get_tx_proof(const std::vector<std::string> &args);
bool check_tx_proof(const std::vector<std::string> &args);
bool show_transfers(const std::vector<std::string> &args);
bool unspent_outputs(const std::vector<std::string> &args);
bool rescan_blockchain(const std::vector<std::string> &args);
- bool refresh_main(uint64_t start_height, bool reset = false);
+ bool refresh_main(uint64_t start_height, bool reset = false, bool is_init = false);
bool set_tx_note(const std::vector<std::string> &args);
bool get_tx_note(const std::vector<std::string> &args);
bool status(const std::vector<std::string> &args);
+ bool wallet_info(const std::vector<std::string> &args);
bool set_default_priority(const std::vector<std::string> &args);
bool sign(const std::vector<std::string> &args);
bool verify(const std::vector<std::string> &args);
@@ -185,6 +191,7 @@ namespace cryptonote
bool accept_loaded_tx(const tools::wallet2::signed_tx_set &txs);
bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr);
std::string get_prompt() const;
+ bool print_seed(bool encrypted);
/*!
* \brief Prints the seed with a nice message
@@ -201,11 +208,17 @@ namespace cryptonote
*/
std::string get_mnemonic_language();
+ /*!
+ * \brief When --do-not-relay option is specified, save the raw tx hex blob to a file instead of calling m_wallet->commit_tx(ptx).
+ * \param ptx_vector Pending tx(es) created by transfer/sweep_all
+ */
+ void commit_or_save(std::vector<tools::wallet2::pending_tx>& ptx_vector, bool do_not_relay);
+
//----------------- 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);
- virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount);
- virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx);
+ 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);
+ virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index);
+ virtual void 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);
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx);
//----------------------------------------------------------
@@ -281,6 +294,7 @@ namespace cryptonote
bool m_allow_mismatched_daemon_version;
bool m_restoring; // are we restoring, by whatever method?
uint64_t m_restore_height; // optional
+ bool m_do_not_relay;
epee::console_handlers_binder m_cmd_binder;
@@ -296,5 +310,6 @@ namespace cryptonote
std::atomic<bool> m_auto_refresh_enabled;
bool m_auto_refresh_refreshing;
std::atomic<bool> m_in_manual_refresh;
+ uint32_t m_current_subaddress_account;
};
}
diff --git a/src/version.cmake b/src/version.cmake
index 623d3cf1f..45a97cd20 100644
--- a/src/version.cmake
+++ b/src/version.cmake
@@ -36,7 +36,7 @@ if(RET)
message(WARNING "Cannot determine current commit. Make sure that you are building either from a Git working tree or from a source archive.")
set(VERSIONTAG "unknown")
- configure_file("src/version.h.in" "${TO}")
+ configure_file("src/version.cpp.in" "${TO}")
else()
message(STATUS "You are currently on commit ${COMMIT}")
@@ -59,5 +59,5 @@ else()
endif()
endif()
- configure_file("src/version.h.in" "${TO}")
+ configure_file("src/version.cpp.in" "${TO}")
endif()
diff --git a/src/version.cpp.in b/src/version.cpp.in
new file mode 100644
index 000000000..d1444f867
--- /dev/null
+++ b/src/version.cpp.in
@@ -0,0 +1,11 @@
+#define DEF_MONERO_VERSION_TAG "@VERSIONTAG@"
+#define DEF_MONERO_VERSION "0.11.0.0"
+#define DEF_MONERO_RELEASE_NAME "Helium Hydra"
+#define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG
+
+#include "version.h"
+
+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;
diff --git a/src/version.h b/src/version.h
new file mode 100644
index 000000000..d1d06c790
--- /dev/null
+++ b/src/version.h
@@ -0,0 +1,6 @@
+#pragma once
+
+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;
diff --git a/src/version.h.in b/src/version.h.in
deleted file mode 100644
index 281b52db4..000000000
--- a/src/version.h.in
+++ /dev/null
@@ -1,4 +0,0 @@
-#define MONERO_VERSION_TAG "@VERSIONTAG@"
-#define MONERO_VERSION "0.11.0.0"
-#define MONERO_RELEASE_NAME "Helium Hydra"
-#define MONERO_VERSION_FULL MONERO_VERSION "-" MONERO_VERSION_TAG
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt
index 639080051..24399790c 100644
--- a/src/wallet/CMakeLists.txt
+++ b/src/wallet/CMakeLists.txt
@@ -41,6 +41,8 @@ set(wallet_sources
api/pending_transaction.cpp
api/utils.cpp
api/address_book.cpp
+ api/subaddress.cpp
+ api/subaddress_account.cpp
api/unsigned_transaction.cpp)
set(wallet_api_headers
@@ -62,6 +64,8 @@ set(wallet_private_headers
api/pending_transaction.h
api/common_defines.h
api/address_book.h
+ api/subaddress.h
+ api/subaddress_account.h
api/unsigned_transaction.h)
monero_private_headers(wallet
@@ -84,7 +88,6 @@ target_link_libraries(wallet
${Boost_REGEX_LIBRARY}
PRIVATE
${EXTRA_LIBRARIES})
-add_dependencies(wallet version)
if (NOT BUILD_GUI_DEPS)
set(wallet_rpc_sources
@@ -110,13 +113,13 @@ if (NOT BUILD_GUI_DEPS)
cryptonote_core
cncrypto
common
+ version
${Boost_CHRONO_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})
- add_dependencies(wallet_rpc_server version)
set_property(TARGET wallet_rpc_server
PROPERTY
OUTPUT_NAME "monero-wallet-rpc")
@@ -126,7 +129,16 @@ endif()
# build and install libwallet_merged only if we building for GUI
if (BUILD_GUI_DEPS)
- set(libs_to_merge wallet cryptonote_core cryptonote_basic mnemonics common cncrypto ringct)
+ set(libs_to_merge
+ wallet
+ cryptonote_core
+ cryptonote_basic
+ mnemonics
+ common
+ cncrypto
+ ringct
+ checkpoints
+ version)
foreach(lib ${libs_to_merge})
list(APPEND objlibs $<TARGET_OBJECTS:obj_${lib}>) # matches naming convention in src/CMakeLists.txt
diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp
index 28f835ebd..da412cd4b 100644
--- a/src/wallet/api/address_book.cpp
+++ b/src/wallet/api/address_book.cpp
@@ -42,22 +42,20 @@ namespace Monero {
AddressBook::~AddressBook() {}
AddressBookImpl::AddressBookImpl(WalletImpl *wallet)
- : m_wallet(wallet) {}
+ : m_wallet(wallet), m_errorCode(Status_Ok) {}
bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &payment_id_str, const std::string &description)
{
clearStatus();
- cryptonote::account_public_address addr;
- bool has_short_pid;
- crypto::hash8 payment_id_short;
- if(!cryptonote::get_account_integrated_address_from_str(addr, has_short_pid, payment_id_short, m_wallet->m_wallet->testnet(), dst_addr)) {
+ cryptonote::address_parse_info info;
+ if(!cryptonote::get_account_address_from_str(info, m_wallet->m_wallet->testnet(), dst_addr)) {
m_errorString = tr("Invalid destination address");
m_errorCode = Invalid_Address;
return false;
}
- crypto::hash payment_id = cryptonote::null_hash;
+ crypto::hash payment_id = crypto::null_hash;
bool has_long_pid = (payment_id_str.empty())? false : tools::wallet2::parse_long_payment_id(payment_id_str, payment_id);
// Short payment id provided
@@ -75,19 +73,19 @@ bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &pa
}
// integrated + long payment id provided
- if(has_long_pid && has_short_pid) {
+ if(has_long_pid && info.has_payment_id) {
m_errorString = tr("Integrated address and long payment id can't be used at the same time");
m_errorCode = Invalid_Payment_Id;
return false;
}
// Pad short pid with zeros
- if (has_short_pid)
+ if (info.has_payment_id)
{
- memcpy(payment_id.data, payment_id_short.data, 8);
+ memcpy(payment_id.data, info.payment_id.data, 8);
}
- bool r = m_wallet->m_wallet->add_address_book_row(addr,payment_id,description);
+ bool r = m_wallet->m_wallet->add_address_book_row(info.address,payment_id,description,info.is_subaddress);
if (r)
refresh();
else
@@ -106,10 +104,10 @@ void AddressBookImpl::refresh()
for (size_t i = 0; i < rows.size(); ++i) {
tools::wallet2::address_book_row * row = &rows.at(i);
- std::string payment_id = (row->m_payment_id == cryptonote::null_hash)? "" : epee::string_tools::pod_to_hex(row->m_payment_id);
- std::string address = cryptonote::get_account_address_as_str(m_wallet->m_wallet->testnet(),row->m_address);
+ std::string payment_id = (row->m_payment_id == crypto::null_hash)? "" : epee::string_tools::pod_to_hex(row->m_payment_id);
+ std::string address = cryptonote::get_account_address_as_str(m_wallet->m_wallet->testnet(), row->m_is_subaddress, row->m_address);
// convert the zero padded short payment id to integrated address
- if (payment_id.length() > 16 && payment_id.substr(16).find_first_not_of('0') == std::string::npos) {
+ if (!row->m_is_subaddress && payment_id.length() > 16 && payment_id.substr(16).find_first_not_of('0') == std::string::npos) {
payment_id = payment_id.substr(0,16);
crypto::hash8 payment_id_short;
if(tools::wallet2::parse_short_payment_id(payment_id, payment_id_short)) {
diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp
index c98a599e7..e17931de1 100644
--- a/src/wallet/api/pending_transaction.cpp
+++ b/src/wallet/api/pending_transaction.cpp
@@ -172,6 +172,22 @@ uint64_t PendingTransactionImpl::txCount() const
return m_pending_tx.size();
}
+std::vector<uint32_t> PendingTransactionImpl::subaddrAccount() const
+{
+ std::vector<uint32_t> result;
+ for (const auto& ptx : m_pending_tx)
+ result.push_back(ptx.construction_data.subaddr_account);
+ return result;
+}
+
+std::vector<std::set<uint32_t>> PendingTransactionImpl::subaddrIndices() const
+{
+ std::vector<std::set<uint32_t>> result;
+ for (const auto& ptx : m_pending_tx)
+ result.push_back(ptx.construction_data.subaddr_indices);
+ return result;
+}
+
}
namespace Bitmonero = Monero;
diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h
index 0c3e95a85..e5b33ac01 100644
--- a/src/wallet/api/pending_transaction.h
+++ b/src/wallet/api/pending_transaction.h
@@ -51,6 +51,8 @@ public:
uint64_t fee() const;
std::vector<std::string> txid() const;
uint64_t txCount() const;
+ std::vector<uint32_t> subaddrAccount() const;
+ std::vector<std::set<uint32_t>> subaddrIndices() const;
// TODO: continue with interface;
private:
diff --git a/src/wallet/api/subaddress.cpp b/src/wallet/api/subaddress.cpp
new file mode 100644
index 000000000..ceda9a9da
--- /dev/null
+++ b/src/wallet/api/subaddress.cpp
@@ -0,0 +1,91 @@
+// Copyright (c) 2017, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "subaddress.h"
+#include "wallet.h"
+#include "crypto/hash.h"
+#include "wallet/wallet2.h"
+#include "common_defines.h"
+
+#include <vector>
+
+namespace Monero {
+
+Subaddress::~Subaddress() {}
+
+SubaddressImpl::SubaddressImpl(WalletImpl *wallet)
+ : m_wallet(wallet) {}
+
+void SubaddressImpl::addRow(uint32_t accountIndex, const std::string &label)
+{
+ m_wallet->m_wallet->add_subaddress(accountIndex, label);
+ refresh(accountIndex);
+}
+
+void SubaddressImpl::setLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label)
+{
+ try
+ {
+ m_wallet->m_wallet->set_subaddress_label({accountIndex, addressIndex}, label);
+ refresh(accountIndex);
+ }
+ catch (const std::exception& e)
+ {
+ LOG_ERROR("setLabel: " << e.what());
+ }
+}
+
+void SubaddressImpl::refresh(uint32_t accountIndex)
+{
+ LOG_PRINT_L2("Refreshing subaddress");
+
+ clearRows();
+ for (size_t i = 0; i < m_wallet->m_wallet->get_num_subaddresses(accountIndex); ++i)
+ {
+ m_rows.push_back(new SubaddressRow(i, m_wallet->m_wallet->get_subaddress_as_str({accountIndex, (uint32_t)i}), m_wallet->m_wallet->get_subaddress_label({accountIndex, (uint32_t)i})));
+ }
+}
+
+void SubaddressImpl::clearRows() {
+ for (auto r : m_rows) {
+ delete r;
+ }
+ m_rows.clear();
+}
+
+std::vector<SubaddressRow*> SubaddressImpl::getAll() const
+{
+ return m_rows;
+}
+
+SubaddressImpl::~SubaddressImpl()
+{
+ clearRows();
+}
+
+} // namespace
diff --git a/src/wallet/api/subaddress.h b/src/wallet/api/subaddress.h
new file mode 100644
index 000000000..e3e28eba1
--- /dev/null
+++ b/src/wallet/api/subaddress.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2017, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "wallet/wallet2_api.h"
+#include "wallet/wallet2.h"
+
+namespace Monero {
+
+class WalletImpl;
+
+class SubaddressImpl : public Subaddress
+{
+public:
+ SubaddressImpl(WalletImpl * wallet);
+ ~SubaddressImpl();
+
+ // Fetches addresses from Wallet2
+ void refresh(uint32_t accountIndex);
+ std::vector<SubaddressRow*> getAll() const;
+ void addRow(uint32_t accountIndex, const std::string &label);
+ void setLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label);
+
+private:
+ void clearRows();
+
+private:
+ WalletImpl *m_wallet;
+ std::vector<SubaddressRow*> m_rows;
+};
+
+}
diff --git a/src/wallet/api/subaddress_account.cpp b/src/wallet/api/subaddress_account.cpp
new file mode 100644
index 000000000..736ef874e
--- /dev/null
+++ b/src/wallet/api/subaddress_account.cpp
@@ -0,0 +1,90 @@
+// Copyright (c) 2017, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "subaddress_account.h"
+#include "wallet.h"
+#include "crypto/hash.h"
+#include "wallet/wallet2.h"
+#include "common_defines.h"
+
+#include <vector>
+
+namespace Monero {
+
+SubaddressAccount::~SubaddressAccount() {}
+
+SubaddressAccountImpl::SubaddressAccountImpl(WalletImpl *wallet)
+ : m_wallet(wallet) {}
+
+void SubaddressAccountImpl::addRow(const std::string &label)
+{
+ m_wallet->m_wallet->add_subaddress_account(label);
+ refresh();
+}
+
+void SubaddressAccountImpl::setLabel(uint32_t accountIndex, const std::string &label)
+{
+ m_wallet->m_wallet->set_subaddress_label({accountIndex, 0}, label);
+ refresh();
+}
+
+void SubaddressAccountImpl::refresh()
+{
+ LOG_PRINT_L2("Refreshing subaddress account");
+
+ clearRows();
+ for (uint32_t i = 0; i < m_wallet->m_wallet->get_num_subaddress_accounts(); ++i)
+ {
+ m_rows.push_back(new SubaddressAccountRow(
+ i,
+ m_wallet->m_wallet->get_subaddress_as_str({i,0}).substr(0,6),
+ m_wallet->m_wallet->get_subaddress_label({i,0}),
+ cryptonote::print_money(m_wallet->m_wallet->balance(i)),
+ cryptonote::print_money(m_wallet->m_wallet->unlocked_balance(i))
+ ));
+ }
+}
+
+void SubaddressAccountImpl::clearRows() {
+ for (auto r : m_rows) {
+ delete r;
+ }
+ m_rows.clear();
+}
+
+std::vector<SubaddressAccountRow*> SubaddressAccountImpl::getAll() const
+{
+ return m_rows;
+}
+
+SubaddressAccountImpl::~SubaddressAccountImpl()
+{
+ clearRows();
+}
+
+} // namespace
diff --git a/src/wallet/api/subaddress_account.h b/src/wallet/api/subaddress_account.h
new file mode 100644
index 000000000..107d7f87f
--- /dev/null
+++ b/src/wallet/api/subaddress_account.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2017, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "wallet/wallet2_api.h"
+#include "wallet/wallet2.h"
+
+namespace Monero {
+
+class WalletImpl;
+
+class SubaddressAccountImpl : public SubaddressAccount
+{
+public:
+ SubaddressAccountImpl(WalletImpl * wallet);
+ ~SubaddressAccountImpl();
+
+ // Fetches addresses from Wallet2
+ void refresh();
+ std::vector<SubaddressAccountRow*> getAll() const;
+ void addRow(const std::string &label);
+ void setLabel(uint32_t accountIndex, const std::string &label);
+
+private:
+ void clearRows();
+
+private:
+ WalletImpl *m_wallet;
+ std::vector<SubaddressAccountRow*> m_rows;
+};
+
+}
diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp
index 23d3905b2..59eca3dd7 100644
--- a/src/wallet/api/transaction_history.cpp
+++ b/src/wallet/api/transaction_history.cpp
@@ -130,15 +130,14 @@ void TransactionHistoryImpl::refresh()
ti->m_direction = TransactionInfo::Direction_In;
ti->m_hash = string_tools::pod_to_hex(pd.m_tx_hash);
ti->m_blockheight = pd.m_block_height;
+ ti->m_subaddrIndex = { pd.m_subaddr_index.minor };
+ ti->m_subaddrAccount = pd.m_subaddr_index.major;
+ ti->m_label = m_wallet->m_wallet->get_subaddress_label(pd.m_subaddr_index);
ti->m_timestamp = pd.m_timestamp;
- ti->m_confirmations = wallet_height - pd.m_block_height;
+ ti->m_confirmations = (wallet_height > pd.m_block_height) ? wallet_height - pd.m_block_height : 0;
ti->m_unlock_time = pd.m_unlock_time;
m_history.push_back(ti);
- /* output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%20.20s %s %s %s")
- % print_money(pd.m_amount)
- % string_tools::pod_to_hex(pd.m_tx_hash)
- % payment_id % "-").str()))); */
}
// confirmed output transactions
@@ -174,12 +173,15 @@ void TransactionHistoryImpl::refresh()
ti->m_direction = TransactionInfo::Direction_Out;
ti->m_hash = string_tools::pod_to_hex(hash);
ti->m_blockheight = pd.m_block_height;
+ ti->m_subaddrIndex = pd.m_subaddr_indices;
+ ti->m_subaddrAccount = pd.m_subaddr_account;
+ ti->m_label = pd.m_subaddr_indices.size() == 1 ? m_wallet->m_wallet->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : "";
ti->m_timestamp = pd.m_timestamp;
- ti->m_confirmations = wallet_height - pd.m_block_height;
+ ti->m_confirmations = (wallet_height > pd.m_block_height) ? wallet_height - pd.m_block_height : 0;
// 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->testnet(), d.addr)});
+ ti->m_transfers.push_back({d.amount, get_account_address_as_str(m_wallet->m_wallet->testnet(), d.is_subaddress, d.addr)});
}
m_history.push_back(ti);
}
@@ -199,12 +201,15 @@ void TransactionHistoryImpl::refresh()
TransactionInfoImpl * ti = new TransactionInfoImpl();
ti->m_paymentid = payment_id;
- ti->m_amount = amount - pd.m_change;
+ ti->m_amount = amount - pd.m_change - fee;
ti->m_fee = fee;
ti->m_direction = TransactionInfo::Direction_Out;
ti->m_failed = is_failed;
ti->m_pending = true;
ti->m_hash = string_tools::pod_to_hex(hash);
+ ti->m_subaddrIndex = pd.m_subaddr_indices;
+ ti->m_subaddrAccount = pd.m_subaddr_account;
+ ti->m_label = pd.m_subaddr_indices.size() == 1 ? m_wallet->m_wallet->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : "";
ti->m_timestamp = pd.m_timestamp;
ti->m_confirmations = 0;
m_history.push_back(ti);
@@ -226,6 +231,9 @@ void TransactionHistoryImpl::refresh()
ti->m_hash = string_tools::pod_to_hex(pd.m_tx_hash);
ti->m_blockheight = pd.m_block_height;
ti->m_pending = true;
+ ti->m_subaddrIndex = { pd.m_subaddr_index.minor };
+ ti->m_subaddrAccount = pd.m_subaddr_index.major;
+ ti->m_label = m_wallet->m_wallet->get_subaddress_label(pd.m_subaddr_index);
ti->m_timestamp = pd.m_timestamp;
ti->m_confirmations = 0;
m_history.push_back(ti);
diff --git a/src/wallet/api/transaction_info.cpp b/src/wallet/api/transaction_info.cpp
index 171272265..1a5df454c 100644
--- a/src/wallet/api/transaction_info.cpp
+++ b/src/wallet/api/transaction_info.cpp
@@ -48,6 +48,7 @@ TransactionInfoImpl::TransactionInfoImpl()
, m_amount(0)
, m_fee(0)
, m_blockheight(0)
+ , m_subaddrAccount(0)
, m_timestamp(0)
, m_confirmations(0)
, m_unlock_time(0)
@@ -91,6 +92,22 @@ uint64_t TransactionInfoImpl::blockHeight() const
return m_blockheight;
}
+std::set<uint32_t> TransactionInfoImpl::subaddrIndex() const
+{
+ return m_subaddrIndex;
+}
+
+uint32_t TransactionInfoImpl::subaddrAccount() const
+{
+ return m_subaddrAccount;
+}
+
+string TransactionInfoImpl::label() const
+{
+ return m_label;
+}
+
+
string TransactionInfoImpl::hash() const
{
return m_hash;
diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h
index ee56b859f..d19ef8899 100644
--- a/src/wallet/api/transaction_info.h
+++ b/src/wallet/api/transaction_info.h
@@ -50,6 +50,9 @@ public:
//! always 0 for incoming txes
virtual uint64_t fee() const;
virtual uint64_t blockHeight() const;
+ virtual std::set<uint32_t> subaddrIndex() const;
+ virtual uint32_t subaddrAccount() const;
+ virtual std::string label() const;
virtual std::string hash() const;
virtual std::time_t timestamp() const;
@@ -65,6 +68,9 @@ private:
uint64_t m_amount;
uint64_t m_fee;
uint64_t m_blockheight;
+ std::set<uint32_t> m_subaddrIndex; // always unique index for incoming transfers; can be multiple indices for outgoing transfers
+ uint32_t m_subaddrAccount;
+ std::string m_label;
std::string m_hash;
std::time_t m_timestamp;
std::string m_paymentid;
diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp
index 961bd772a..4c8c5ade2 100644
--- a/src/wallet/api/unsigned_transaction.cpp
+++ b/src/wallet/api/unsigned_transaction.cpp
@@ -102,12 +102,38 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_nu
// gather info to ask the user
uint64_t amount = 0, amount_to_dests = 0, change = 0;
size_t min_ring_size = ~0;
- std::unordered_map<std::string, uint64_t> dests;
- const std::string wallet_address = m_wallet.m_wallet->get_account().get_public_address_str(m_wallet.m_wallet->testnet());
+ std::unordered_map<cryptonote::account_public_address, std::pair<std::string, uint64_t>> dests;
int first_known_non_zero_change_index = -1;
+ std::string payment_id_string = "";
for (size_t n = 0; n < get_num_txes(); ++n)
{
const tools::wallet2::tx_construction_data &cd = get_tx(n);
+
+ std::vector<cryptonote::tx_extra_field> tx_extra_fields;
+ bool has_encrypted_payment_id = false;
+ crypto::hash8 payment_id8 = crypto::null_hash8;
+ if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields))
+ {
+ cryptonote::tx_extra_nonce extra_nonce;
+ if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
+ {
+ crypto::hash payment_id;
+ if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
+ {
+ if (!payment_id_string.empty())
+ payment_id_string += ", ";
+ payment_id_string = std::string("encrypted payment ID ") + epee::string_tools::pod_to_hex(payment_id8);
+ has_encrypted_payment_id = true;
+ }
+ else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
+ {
+ if (!payment_id_string.empty())
+ payment_id_string += ", ";
+ payment_id_string = std::string("unencrypted payment ID ") + epee::string_tools::pod_to_hex(payment_id);
+ }
+ }
+ }
+
for (size_t s = 0; s < cd.sources.size(); ++s)
{
amount += cd.sources[s].amount;
@@ -118,24 +144,31 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_nu
for (size_t d = 0; d < cd.splitted_dsts.size(); ++d)
{
const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d];
- std::string address = get_account_address_as_str(m_wallet.m_wallet->testnet(), entry.addr);
- std::unordered_map<std::string,uint64_t>::iterator i = dests.find(address);
+ std::string address, standard_address = get_account_address_as_str(m_wallet.testnet(), entry.is_subaddress, entry.addr);
+ if (has_encrypted_payment_id && !entry.is_subaddress)
+ {
+ address = get_account_integrated_address_as_str(m_wallet.testnet(), entry.addr, payment_id8);
+ address += std::string(" (" + standard_address + " with encrypted payment id " + epee::string_tools::pod_to_hex(payment_id8) + ")");
+ }
+ else
+ address = standard_address;
+ auto i = dests.find(entry.addr);
if (i == dests.end())
- dests.insert(std::make_pair(address, entry.amount));
+ dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount)));
else
- i->second += entry.amount;
+ i->second.second += entry.amount;
amount_to_dests += entry.amount;
}
if (cd.change_dts.amount > 0)
{
- std::unordered_map<std::string, uint64_t>::iterator it = dests.find(get_account_address_as_str(m_wallet.m_wallet->testnet(), cd.change_dts.addr));
+ auto it = dests.find(cd.change_dts.addr);
if (it == dests.end())
{
m_status = Status_Error;
m_errorString = tr("Claimed change does not go to a paid address");
return false;
}
- if (it->second < cd.change_dts.amount)
+ if (it->second.second < cd.change_dts.amount)
{
m_status = Status_Error;
m_errorString = tr("Claimed change is larger than payment to the change address");
@@ -153,15 +186,15 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_nu
}
}
change += cd.change_dts.amount;
- it->second -= cd.change_dts.amount;
- if (it->second == 0)
- dests.erase(get_account_address_as_str(m_wallet.m_wallet->testnet(), cd.change_dts.addr));
+ it->second.second -= cd.change_dts.amount;
+ if (it->second.second == 0)
+ dests.erase(cd.change_dts.addr);
}
}
std::string dest_string;
- for (std::unordered_map<std::string, uint64_t>::const_iterator i = dests.begin(); i != dests.end(); )
+ for (auto i = dests.begin(); i != dests.end(); )
{
- dest_string += (boost::format(tr("sending %s to %s")) % cryptonote::print_money(i->second) % i->first).str();
+ dest_string += (boost::format(tr("sending %s to %s")) % cryptonote::print_money(i->second.second) % i->second.first).str();
++i;
if (i != dests.end())
dest_string += ", ";
@@ -172,7 +205,7 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_nu
std::string change_string;
if (change > 0)
{
- std::string address = get_account_address_as_str(m_wallet.m_wallet->testnet(), get_tx(0).change_dts.addr);
+ std::string address = get_account_address_as_str(m_wallet.m_wallet->testnet(), get_tx(0).subaddr_account > 0, get_tx(0).change_dts.addr);
change_string += (boost::format(tr("%s change to %s")) % cryptonote::print_money(change) % address).str();
}
else
@@ -230,13 +263,13 @@ std::vector<std::string> UnsignedTransactionImpl::paymentId() const
{
std::vector<string> result;
for (const auto &utx: m_unsigned_tx_set.txes) {
- crypto::hash payment_id = cryptonote::null_hash;
+ crypto::hash payment_id = crypto::null_hash;
cryptonote::tx_extra_nonce extra_nonce;
std::vector<cryptonote::tx_extra_field> tx_extra_fields;
cryptonote::parse_tx_extra(utx.extra, tx_extra_fields);
if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
{
- crypto::hash8 payment_id8 = cryptonote::null_hash8;
+ crypto::hash8 payment_id8 = crypto::null_hash8;
if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
{
// We can't decrypt short pid without recipient key.
@@ -244,10 +277,10 @@ std::vector<std::string> UnsignedTransactionImpl::paymentId() const
}
else if (!cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
{
- payment_id = cryptonote::null_hash;
+ payment_id = crypto::null_hash;
}
}
- if(payment_id != cryptonote::null_hash)
+ if(payment_id != crypto::null_hash)
result.push_back(epee::string_tools::pod_to_hex(payment_id));
else
result.push_back("");
@@ -260,7 +293,7 @@ std::vector<std::string> UnsignedTransactionImpl::recipientAddress() const
// TODO: return integrated address if short payment ID exists
std::vector<string> result;
for (const auto &utx: m_unsigned_tx_set.txes) {
- result.push_back(cryptonote::get_account_address_as_str(m_wallet.m_wallet->testnet(), utx.dests[0].addr));
+ result.push_back(cryptonote::get_account_address_as_str(m_wallet.m_wallet->testnet(), utx.dests[0].is_subaddress, utx.dests[0].addr));
}
return result;
}
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 7afc1f449..8f7befc8c 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -34,6 +34,8 @@
#include "unsigned_transaction.h"
#include "transaction_history.h"
#include "address_book.h"
+#include "subaddress.h"
+#include "subaddress_account.h"
#include "common_defines.h"
#include "common/util.h"
@@ -100,14 +102,15 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
}
}
- virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount)
+ 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)
{
std::string tx_hash = epee::string_tools::pod_to_hex(txid);
LOG_PRINT_L3(__FUNCTION__ << ": money received. height: " << height
<< ", tx: " << tx_hash
- << ", amount: " << print_money(amount));
+ << ", amount: " << print_money(amount)
+ << ", idx: " << subaddr_index);
// do not signal on received tx if wallet is not syncronized completely
if (m_listener && m_wallet->synchronized()) {
m_listener->moneyReceived(tx_hash, amount);
@@ -115,14 +118,15 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
}
}
- virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount)
+ virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index)
{
std::string tx_hash = epee::string_tools::pod_to_hex(txid);
LOG_PRINT_L3(__FUNCTION__ << ": unconfirmed money received. height: " << height
<< ", tx: " << tx_hash
- << ", amount: " << print_money(amount));
+ << ", amount: " << print_money(amount)
+ << ", idx: " << subaddr_index);
// do not signal on received tx if wallet is not syncronized completely
if (m_listener && m_wallet->synchronized()) {
m_listener->unconfirmedMoneyReceived(tx_hash, amount);
@@ -131,13 +135,14 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
}
virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx,
- uint64_t amount, const cryptonote::transaction& spend_tx)
+ uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index)
{
// TODO;
std::string tx_hash = epee::string_tools::pod_to_hex(txid);
LOG_PRINT_L3(__FUNCTION__ << ": money spent. height: " << height
<< ", tx: " << tx_hash
- << ", amount: " << print_money(amount));
+ << ", amount: " << print_money(amount)
+ << ", idx: " << subaddr_index);
// do not signal on sent tx if wallet is not syncronized completely
if (m_listener && m_wallet->synchronized()) {
m_listener->moneySpent(tx_hash, amount);
@@ -150,6 +155,38 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
// TODO;
}
+ // Light wallet callbacks
+ virtual void on_lw_new_block(uint64_t height)
+ {
+ if (m_listener) {
+ m_listener->newBlock(height);
+ }
+ }
+
+ virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount)
+ {
+ if (m_listener) {
+ std::string tx_hash = epee::string_tools::pod_to_hex(txid);
+ m_listener->moneyReceived(tx_hash, amount);
+ }
+ }
+
+ virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount)
+ {
+ if (m_listener) {
+ std::string tx_hash = epee::string_tools::pod_to_hex(txid);
+ m_listener->unconfirmedMoneyReceived(tx_hash, amount);
+ }
+ }
+
+ virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount)
+ {
+ if (m_listener) {
+ std::string tx_hash = epee::string_tools::pod_to_hex(txid);
+ m_listener->moneySpent(tx_hash, amount);
+ }
+ }
+
WalletListener * m_listener;
WalletImpl * m_wallet;
};
@@ -198,18 +235,14 @@ bool Wallet::paymentIdValid(const string &paiment_id)
bool Wallet::addressValid(const std::string &str, bool testnet)
{
- bool has_payment_id;
- cryptonote::account_public_address address;
- crypto::hash8 pid;
- return get_account_integrated_address_from_str(address, has_payment_id, pid, testnet, str);
+ cryptonote::address_parse_info info;
+ return get_account_address_from_str(info, testnet, str);
}
bool Wallet::keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error)
{
- bool has_payment_id;
- cryptonote::account_public_address address;
- crypto::hash8 pid;
- if(!get_account_integrated_address_from_str(address, has_payment_id, pid, testnet, address_string)) {
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, testnet, address_string)) {
error = tr("Failed to parse address");
return false;
}
@@ -230,9 +263,9 @@ bool Wallet::keyValid(const std::string &secret_key_string, const std::string &a
}
bool matchAddress = false;
if(isViewKey)
- matchAddress = address.m_view_public_key == pkey;
+ matchAddress = info.address.m_view_public_key == pkey;
else
- matchAddress = address.m_spend_public_key == pkey;
+ matchAddress = info.address.m_spend_public_key == pkey;
if(!matchAddress) {
error = tr("key does not match address");
@@ -244,14 +277,12 @@ bool Wallet::keyValid(const std::string &secret_key_string, const std::string &a
std::string Wallet::paymentIdFromAddress(const std::string &str, bool testnet)
{
- bool has_payment_id;
- cryptonote::account_public_address address;
- crypto::hash8 pid;
- if (!get_account_integrated_address_from_str(address, has_payment_id, pid, testnet, str))
+ cryptonote::address_parse_info info;
+ if (!get_account_address_from_str(info, testnet, str))
return "";
- if (!has_payment_id)
+ if (!info.has_payment_id)
return "";
- return epee::string_tools::pod_to_hex(pid);
+ return epee::string_tools::pod_to_hex(info.payment_id);
}
uint64_t Wallet::maximumAllowedAmount()
@@ -286,6 +317,8 @@ WalletImpl::WalletImpl(bool testnet)
m_refreshThreadDone = false;
m_refreshEnabled = false;
m_addressBook = new AddressBookImpl(this);
+ m_subaddress = new SubaddressImpl(this);
+ m_subaddressAccount = new SubaddressAccountImpl(this);
m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS;
@@ -303,12 +336,14 @@ WalletImpl::~WalletImpl()
// Pause refresh thread - prevents refresh from starting again
pauseRefresh();
// Close wallet - stores cache and stops ongoing refresh operation
- close();
+ close(false); // do not store wallet as part of the closing activities
// Stop refresh thread
stopRefresh();
delete m_wallet2Callback;
delete m_history;
delete m_addressBook;
+ delete m_subaddress;
+ delete m_subaddressAccount;
delete m_wallet;
LOG_PRINT_L1(__FUNCTION__ << " finished");
}
@@ -423,10 +458,8 @@ bool WalletImpl::recoverFromKeys(const std::string &path,
const std::string &viewkey_string,
const std::string &spendkey_string)
{
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, m_wallet->testnet(), address_string))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, m_wallet->testnet(), address_string))
{
m_errorString = tr("failed to parse address");
m_status = Status_Error;
@@ -471,7 +504,7 @@ bool WalletImpl::recoverFromKeys(const std::string &path,
m_status = Status_Error;
return false;
}
- if (address.m_spend_public_key != pkey) {
+ if (info.address.m_spend_public_key != pkey) {
m_errorString = tr("spend key does not match address");
m_status = Status_Error;
return false;
@@ -482,7 +515,7 @@ bool WalletImpl::recoverFromKeys(const std::string &path,
m_status = Status_Error;
return false;
}
- if (address.m_view_public_key != pkey) {
+ if (info.address.m_view_public_key != pkey) {
m_errorString = tr("view key does not match address");
m_status = Status_Error;
return false;
@@ -491,12 +524,12 @@ bool WalletImpl::recoverFromKeys(const std::string &path,
try
{
if (has_spendkey) {
- m_wallet->generate(path, "", address, spendkey, viewkey);
+ m_wallet->generate(path, "", info.address, spendkey, viewkey);
setSeedLanguage(language);
LOG_PRINT_L1("Generated new wallet from keys with seed language: " + language);
}
else {
- m_wallet->generate(path, "", address, viewkey);
+ m_wallet->generate(path, "", info.address, viewkey);
LOG_PRINT_L1("Generated new view only wallet from keys");
}
@@ -566,19 +599,21 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed)
return m_status == Status_Ok;
}
-bool WalletImpl::close()
+bool WalletImpl::close(bool store)
{
bool result = false;
LOG_PRINT_L1("closing wallet...");
try {
- // Do not store wallet with invalid status
- // Status Critical refers to errors on opening or creating wallets.
- if (status() != Status_Critical)
- m_wallet->store();
- else
- LOG_ERROR("Status_Critical - not storing wallet");
- LOG_PRINT_L1("wallet::store done");
+ if (store) {
+ // Do not store wallet with invalid status
+ // Status Critical refers to errors on opening or creating wallets.
+ if (status() != Status_Critical)
+ m_wallet->store();
+ else
+ LOG_ERROR("Status_Critical - not storing wallet");
+ LOG_PRINT_L1("wallet::store done");
+ }
LOG_PRINT_L1("Calling wallet::stop...");
m_wallet->stop();
LOG_PRINT_L1("wallet::stop done");
@@ -633,9 +668,9 @@ bool WalletImpl::setPassword(const std::string &password)
return m_status == Status_Ok;
}
-std::string WalletImpl::address() const
+std::string WalletImpl::address(uint32_t accountIndex, uint32_t addressIndex) const
{
- return m_wallet->get_account().get_public_address_str(m_wallet->testnet());
+ return m_wallet->get_subaddress_as_str({accountIndex, addressIndex});
}
std::string WalletImpl::integratedAddress(const std::string &payment_id) const
@@ -644,7 +679,7 @@ std::string WalletImpl::integratedAddress(const std::string &payment_id) const
if (!tools::wallet2::parse_short_payment_id(payment_id, pid)) {
return "";
}
- return m_wallet->get_account().get_public_integrated_address_str(pid, m_wallet->testnet());
+ return m_wallet->get_integrated_address_as_str(pid);
}
std::string WalletImpl::secretViewKey() const
@@ -700,12 +735,45 @@ string WalletImpl::keysFilename() const
return m_wallet->get_keys_file();
}
-bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit, const std::string &daemon_username, const std::string &daemon_password)
+bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit, const std::string &daemon_username, const std::string &daemon_password, bool use_ssl, bool lightWallet)
{
clearStatus();
+ m_wallet->set_light_wallet(lightWallet);
if(daemon_username != "")
m_daemon_login.emplace(daemon_username, daemon_password);
- return doInit(daemon_address, upper_transaction_size_limit);
+ return doInit(daemon_address, upper_transaction_size_limit, use_ssl);
+}
+
+bool WalletImpl::lightWalletLogin(bool &isNewWallet) const
+{
+ return m_wallet->light_wallet_login(isNewWallet);
+}
+
+bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status)
+{
+ try
+ {
+ cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response response;
+ if(!m_wallet->light_wallet_import_wallet_request(response)){
+ m_errorString = tr("Failed to send import wallet request");
+ m_status = Status_Error;
+ return false;
+ }
+ fee = response.import_fee;
+ payment_id = response.payment_id;
+ new_request = response.new_request;
+ request_fulfilled = response.request_fulfilled;
+ payment_address = response.payment_address;
+ status = response.status;
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR("Error sending import wallet request: " << e.what());
+ m_errorString = e.what();
+ m_status = Status_Error;
+ return false;
+ }
+ return true;
}
void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height)
@@ -718,18 +786,21 @@ void WalletImpl::setRecoveringFromSeed(bool recoveringFromSeed)
m_recoveringFromSeed = recoveringFromSeed;
}
-uint64_t WalletImpl::balance() const
+uint64_t WalletImpl::balance(uint32_t accountIndex) const
{
- return m_wallet->balance();
+ return m_wallet->balance(accountIndex);
}
-uint64_t WalletImpl::unlockedBalance() const
+uint64_t WalletImpl::unlockedBalance(uint32_t accountIndex) const
{
- return m_wallet->unlocked_balance();
+ return m_wallet->unlocked_balance(accountIndex);
}
uint64_t WalletImpl::blockChainHeight() const
{
+ if(m_wallet->light_wallet()) {
+ return m_wallet->get_light_wallet_scanned_block_height();
+ }
return m_wallet->get_blockchain_current_height();
}
uint64_t WalletImpl::approximateBlockChainHeight() const
@@ -738,6 +809,9 @@ uint64_t WalletImpl::approximateBlockChainHeight() const
}
uint64_t WalletImpl::daemonBlockChainHeight() const
{
+ if(m_wallet->light_wallet()) {
+ return m_wallet->get_light_wallet_scanned_block_height();
+ }
if (!m_is_connected)
return 0;
std::string err;
@@ -757,6 +831,9 @@ uint64_t WalletImpl::daemonBlockChainHeight() const
uint64_t WalletImpl::daemonBlockChainTargetHeight() const
{
+ if(m_wallet->light_wallet()) {
+ return m_wallet->get_light_wallet_blockchain_height();
+ }
if (!m_is_connected)
return 0;
std::string err;
@@ -912,6 +989,50 @@ bool WalletImpl::importKeyImages(const string &filename)
return true;
}
+void WalletImpl::addSubaddressAccount(const std::string& label)
+{
+ m_wallet->add_subaddress_account(label);
+}
+size_t WalletImpl::numSubaddressAccounts() const
+{
+ return m_wallet->get_num_subaddress_accounts();
+}
+size_t WalletImpl::numSubaddresses(uint32_t accountIndex) const
+{
+ return m_wallet->get_num_subaddresses(accountIndex);
+}
+void WalletImpl::addSubaddress(uint32_t accountIndex, const std::string& label)
+{
+ m_wallet->add_subaddress(accountIndex, label);
+}
+std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const
+{
+ try
+ {
+ return m_wallet->get_subaddress_label({accountIndex, addressIndex});
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR("Error getting subaddress label: ") << e.what();
+ m_errorString = string(tr("Failed to get subaddress label: ")) + e.what();
+ m_status = Status_Error;
+ return "";
+ }
+}
+void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label)
+{
+ try
+ {
+ return m_wallet->set_subaddress_label({accountIndex, addressIndex}, label);
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR("Error setting subaddress label: ") << e.what();
+ m_errorString = string(tr("Failed to set subaddress label: ")) + e.what();
+ m_status = Status_Error;
+ }
+}
+
// TODO:
// 1 - properly handle payment id (add another menthod with explicit 'payment_id' param)
// 2 - check / design how "Transaction" can be single interface
@@ -923,18 +1044,16 @@ bool WalletImpl::importKeyImages(const string &filename)
// - confirmed_transfer_details)
PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, optional<uint64_t> amount, uint32_t mixin_count,
- PendingTransaction::Priority priority)
+ PendingTransaction::Priority priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices)
{
clearStatus();
// Pause refresh thread while creating transaction
pauseRefresh();
- cryptonote::account_public_address addr;
+ cryptonote::address_parse_info info;
// indicates if dst_addr is integrated address (address + payment_id)
- bool has_payment_id;
- crypto::hash8 payment_id_short;
// TODO: (https://bitcointalk.org/index.php?topic=753252.msg9985441#msg9985441)
size_t fake_outs_count = mixin_count > 0 ? mixin_count : m_wallet->default_mixin();
if (fake_outs_count == 0)
@@ -943,7 +1062,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
do {
- if(!cryptonote::get_account_integrated_address_from_str(addr, has_payment_id, payment_id_short, m_wallet->testnet(), dst_addr)) {
+ if(!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), dst_addr)) {
// TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982
m_status = Status_Error;
m_errorString = "Invalid destination address";
@@ -953,7 +1072,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
std::vector<uint8_t> extra;
// if dst_addr is not an integrated address, parse payment_id
- if (!has_payment_id && !payment_id.empty()) {
+ if (!info.has_payment_id && !payment_id.empty()) {
// copy-pasted from simplewallet.cpp:2212
crypto::hash payment_id_long;
bool r = tools::wallet2::parse_long_payment_id(payment_id, payment_id_long);
@@ -962,10 +1081,10 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_long);
r = add_extra_nonce_to_tx_extra(extra, extra_nonce);
} else {
- r = tools::wallet2::parse_short_payment_id(payment_id, payment_id_short);
+ r = tools::wallet2::parse_short_payment_id(payment_id, info.payment_id);
if (r) {
std::string extra_nonce;
- set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_short);
+ set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id);
r = add_extra_nonce_to_tx_extra(extra, extra_nonce);
}
}
@@ -976,13 +1095,13 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
break;
}
}
- else if (has_payment_id) {
+ else if (info.has_payment_id) {
std::string extra_nonce;
- set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_short);
+ set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id);
bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce);
if (!r) {
m_status = Status_Error;
- m_errorString = tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(payment_id_short);
+ m_errorString = tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id);
break;
}
}
@@ -994,16 +1113,23 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
if (amount) {
vector<cryptonote::tx_destination_entry> dsts;
cryptonote::tx_destination_entry de;
- de.addr = addr;
+ de.addr = info.address;
de.amount = *amount;
+ de.is_subaddress = info.is_subaddress;
dsts.push_back(de);
transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */,
static_cast<uint32_t>(priority),
- extra, m_trustedDaemon);
+ extra, subaddr_account, subaddr_indices, m_trustedDaemon);
} else {
- transaction->m_pending_tx = m_wallet->create_transactions_all(0, addr, fake_outs_count, 0 /* unlock_time */,
+ // for the GUI, sweep_all (i.e. amount set as "(all)") will always sweep all the funds in all the addresses
+ if (subaddr_indices.empty())
+ {
+ for (uint32_t index = 0; index < m_wallet->get_num_subaddresses(subaddr_account); ++index)
+ subaddr_indices.insert(index);
+ }
+ transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */,
static_cast<uint32_t>(priority),
- extra, m_trustedDaemon);
+ extra, subaddr_account, subaddr_indices, m_trustedDaemon);
}
} catch (const tools::error::daemon_busy&) {
@@ -1184,16 +1310,26 @@ void WalletImpl::disposeTransaction(PendingTransaction *t)
delete t;
}
-TransactionHistory *WalletImpl::history() const
+TransactionHistory *WalletImpl::history()
{
return m_history;
}
-AddressBook *WalletImpl::addressBook() const
+AddressBook *WalletImpl::addressBook()
{
return m_addressBook;
}
+Subaddress *WalletImpl::subaddress()
+{
+ return m_subaddress;
+}
+
+SubaddressAccount *WalletImpl::subaddressAccount()
+{
+ return m_subaddressAccount;
+}
+
void WalletImpl::setListener(WalletListener *l)
{
// TODO thread synchronization;
@@ -1241,7 +1377,8 @@ std::string WalletImpl::getTxKey(const std::string &txid) const
const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
crypto::secret_key tx_key;
- if (m_wallet->get_tx_key(htxid, tx_key))
+ std::vector<crypto::secret_key> additional_tx_keys;
+ if (m_wallet->get_tx_key(htxid, tx_key, additional_tx_keys))
{
return epee::string_tools::pod_to_hex(tx_key);
}
@@ -1258,14 +1395,12 @@ std::string WalletImpl::signMessage(const std::string &message)
bool WalletImpl::verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const
{
- cryptonote::account_public_address addr;
- bool has_payment_id;
- crypto::hash8 payment_id;
+ cryptonote::address_parse_info info;
- if (!cryptonote::get_account_integrated_address_from_str(addr, has_payment_id, payment_id, m_wallet->testnet(), address))
+ if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address))
return false;
- return m_wallet->verify(message, addr, signature);
+ return m_wallet->verify(message, info.address, signature);
}
bool WalletImpl::connectToDaemon()
@@ -1286,7 +1421,8 @@ Wallet::ConnectionStatus WalletImpl::connected() const
m_is_connected = m_wallet->check_connection(&version, DEFAULT_CONNECTION_TIMEOUT_MILLIS);
if (!m_is_connected)
return Wallet::ConnectionStatus_Disconnected;
- if ((version >> 16) != CORE_RPC_VERSION_MAJOR)
+ // Version check is not implemented in light wallets nodes/wallets
+ if (!m_wallet->light_wallet() && (version >> 16) != CORE_RPC_VERSION_MAJOR)
return Wallet::ConnectionStatus_WrongVersion;
return Wallet::ConnectionStatus_Connected;
}
@@ -1349,7 +1485,7 @@ void WalletImpl::doRefresh()
try {
// Syncing daemon and refreshing wallet simultaneously is very resource intensive.
// Disable refresh if wallet is disconnected or daemon isn't synced.
- if (daemonSynced()) {
+ if (m_wallet->light_wallet() || daemonSynced()) {
m_wallet->refresh();
if (!m_synchronized) {
m_synchronized = true;
@@ -1414,13 +1550,14 @@ bool WalletImpl::isNewWallet() const
return !(blockChainHeight() > 1 || m_recoveringFromSeed || m_rebuildWalletCache) && !watchOnly();
}
-bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit)
+bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl)
{
- if (!m_wallet->init(daemon_address, m_daemon_login, upper_transaction_size_limit))
+ if (!m_wallet->init(daemon_address, m_daemon_login, upper_transaction_size_limit, ssl))
return false;
// in case new wallet, this will force fast-refresh (pulling hashes instead of blocks)
// If daemon isn't synced a calculated block height will be used instead
+ //TODO: Handle light wallet scenario where block height = 0.
if (isNewWallet() && daemonSynced()) {
LOG_PRINT_L2(__FUNCTION__ << ":New Wallet - fast refresh until " << daemonBlockChainHeight());
m_wallet->set_refresh_from_block_height(daemonBlockChainHeight());
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index 8190c7873..051eda3ba 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -45,6 +45,8 @@ class TransactionHistoryImpl;
class PendingTransactionImpl;
class UnsignedTransactionImpl;
class AddressBookImpl;
+class SubaddressImpl;
+class SubaddressAccountImpl;
struct Wallet2CallbackImpl;
class WalletImpl : public Wallet
@@ -63,7 +65,7 @@ public:
const std::string &address_string,
const std::string &viewkey_string,
const std::string &spendkey_string = "");
- bool close();
+ bool close(bool store = true);
std::string seed() const;
std::string getSeedLanguage() const;
void setSeedLanguage(const std::string &arg);
@@ -71,7 +73,7 @@ public:
int status() const;
std::string errorString() const;
bool setPassword(const std::string &password);
- std::string address() const;
+ std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const;
std::string integratedAddress(const std::string &payment_id) const;
std::string secretViewKey() const;
std::string publicViewKey() const;
@@ -81,13 +83,13 @@ public:
bool store(const std::string &path);
std::string filename() const;
std::string keysFilename() const;
- bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, const std::string &daemon_username = "", const std::string &daemon_password = "");
+ bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, const std::string &daemon_username = "", const std::string &daemon_password = "", bool use_ssl = false, bool lightWallet = false);
bool connectToDaemon();
ConnectionStatus connected() const;
void setTrustedDaemon(bool arg);
bool trustedDaemon() const;
- uint64_t balance() const;
- uint64_t unlockedBalance() const;
+ uint64_t balance(uint32_t accountIndex = 0) const;
+ uint64_t unlockedBalance(uint32_t accountIndex = 0) const;
uint64_t blockChainHeight() const;
uint64_t approximateBlockChainHeight() const;
uint64_t daemonBlockChainHeight() const;
@@ -106,9 +108,18 @@ public:
void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const;
bool useForkRules(uint8_t version, int64_t early_blocks) const;
+ void addSubaddressAccount(const std::string& label);
+ size_t numSubaddressAccounts() const;
+ size_t numSubaddresses(uint32_t accountIndex) const;
+ void addSubaddress(uint32_t accountIndex, const std::string& label);
+ std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const;
+ void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label);
+
PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id,
optional<uint64_t> amount, uint32_t mixin_count,
- PendingTransaction::Priority priority = PendingTransaction::Priority_Low);
+ PendingTransaction::Priority priority = PendingTransaction::Priority_Low,
+ uint32_t subaddr_account = 0,
+ std::set<uint32_t> subaddr_indices = {});
virtual PendingTransaction * createSweepUnmixableTransaction();
bool submitTransaction(const std::string &fileName);
virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename);
@@ -116,8 +127,10 @@ public:
bool importKeyImages(const std::string &filename);
virtual void disposeTransaction(PendingTransaction * t);
- virtual TransactionHistory * history() const;
- virtual AddressBook * addressBook() const;
+ virtual TransactionHistory * history();
+ virtual AddressBook * addressBook();
+ virtual Subaddress * subaddress();
+ virtual SubaddressAccount * subaddressAccount();
virtual void setListener(WalletListener * l);
virtual uint32_t defaultMixin() const;
virtual void setDefaultMixin(uint32_t arg);
@@ -130,6 +143,8 @@ public:
virtual void pauseRefresh();
virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error);
virtual std::string getDefaultDataDir() const;
+ virtual bool lightWalletLogin(bool &isNewWallet) const;
+ virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status);
private:
void clearStatus() const;
@@ -138,7 +153,7 @@ private:
bool daemonSynced() const;
void stopRefresh();
bool isNewWallet() const;
- bool doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit);
+ bool doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, bool ssl = false);
private:
friend class PendingTransactionImpl;
@@ -146,6 +161,8 @@ private:
friend class TransactionHistoryImpl;
friend struct Wallet2CallbackImpl;
friend class AddressBookImpl;
+ friend class SubaddressImpl;
+ friend class SubaddressAccountImpl;
tools::wallet2 * m_wallet;
mutable std::atomic<int> m_status;
@@ -153,9 +170,10 @@ private:
std::string m_password;
TransactionHistoryImpl * m_history;
bool m_trustedDaemon;
- WalletListener * m_walletListener;
Wallet2CallbackImpl * m_wallet2Callback;
AddressBookImpl * m_addressBook;
+ SubaddressImpl * m_subaddress;
+ SubaddressAccountImpl * m_subaddressAccount;
// multi-threaded refresh stuff
std::atomic<bool> m_refreshEnabled;
diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp
index a23533530..a64766c84 100644
--- a/src/wallet/api/wallet_manager.cpp
+++ b/src/wallet/api/wallet_manager.cpp
@@ -102,10 +102,12 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path,
return wallet;
}
-bool WalletManagerImpl::closeWallet(Wallet *wallet)
+bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store)
{
WalletImpl * wallet_ = dynamic_cast<WalletImpl*>(wallet);
- bool result = wallet_->close();
+ if (!wallet_)
+ return false;
+ bool result = wallet_->close(store);
if (!result) {
m_errorString = wallet_->errorString();
} else {
@@ -213,10 +215,8 @@ bool WalletManagerImpl::checkPayment(const std::string &address_text, const std:
tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data());
bool testnet = address_text[0] != '4';
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 payment_id;
- if(!cryptonote::get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_text))
+ cryptonote::address_parse_info info;
+ if(!cryptonote::get_account_address_from_str(info, testnet, address_text))
{
error = tr("failed to parse address");
return false;
@@ -256,7 +256,7 @@ bool WalletManagerImpl::checkPayment(const std::string &address_text, const std:
}
crypto::key_derivation derivation;
- if (!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation))
+ if (!crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation))
{
error = tr("failed to generate key derivation from supplied parameters");
return false;
@@ -270,7 +270,7 @@ bool WalletManagerImpl::checkPayment(const std::string &address_text, const std:
continue;
const cryptonote::txout_to_key tx_out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[n].target);
crypto::public_key pubkey;
- derive_public_key(derivation, n, address.m_spend_public_key, pubkey);
+ derive_public_key(derivation, n, info.address.m_spend_public_key, pubkey);
if (pubkey == tx_out_to_key.key)
{
uint64_t amount;
@@ -285,7 +285,7 @@ bool WalletManagerImpl::checkPayment(const std::string &address_text, const std:
rct::key Ctmp;
//rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key)));
crypto::key_derivation derivation;
- bool r = crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation);
+ bool r = crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation);
if (!r)
{
LOG_ERROR("Failed to generate key derivation to decode rct output " << n);
@@ -320,11 +320,11 @@ bool WalletManagerImpl::checkPayment(const std::string &address_text, const std:
if (received > 0)
{
- LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid);
+ LOG_PRINT_L1(get_account_address_as_str(testnet, info.is_subaddress, info.address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid);
}
else
{
- LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received nothing in txid") << " " << txid);
+ LOG_PRINT_L1(get_account_address_as_str(testnet, info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid);
}
if (res.txs.front().in_pool)
{
diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h
index aa6ea439e..8455f0f16 100644
--- a/src/wallet/api/wallet_manager.h
+++ b/src/wallet/api/wallet_manager.h
@@ -48,7 +48,7 @@ public:
const std::string &addressString,
const std::string &viewKeyString,
const std::string &spendKeyString = "");
- virtual bool closeWallet(Wallet *wallet);
+ virtual bool closeWallet(Wallet *wallet, bool store = true);
bool walletExists(const std::string &path);
bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const;
std::vector<std::string> findWallets(const std::string &path);
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index f72d281c7..22afac7b7 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -33,6 +33,7 @@
#include <boost/format.hpp>
#include <boost/optional/optional.hpp>
#include <boost/utility/value_init.hpp>
+#include <boost/algorithm/string/join.hpp>
#include "include_base_utils.h"
using namespace epee;
@@ -80,8 +81,8 @@ using namespace cryptonote;
// arbitrary, used to generate different hashes from the same input
#define CHACHA8_KEY_TAIL 0x8c
-#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003"
-#define SIGNED_TX_PREFIX "Monero signed tx set\003"
+#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\004"
+#define SIGNED_TX_PREFIX "Monero signed tx set\004"
#define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone
#define RECENT_OUTPUT_ZONE ((time_t)(1.8 * 86400)) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al)
@@ -90,6 +91,9 @@ using namespace cryptonote;
#define SECOND_OUTPUT_RELATEDNESS_THRESHOLD 0.0f
+#define SUBADDRESS_LOOKAHEAD_MAJOR 50
+#define SUBADDRESS_LOOKAHEAD_MINOR 200
+
#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002"
namespace
@@ -289,6 +293,13 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
return false;
}
restore_deterministic_wallet = true;
+
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_passphrase, std::string, String, false, std::string());
+ if (field_seed_passphrase_found)
+ {
+ if (!field_seed_passphrase.empty())
+ recovery_key = cryptonote::decrypt_key(recovery_key, field_seed_passphrase);
+ }
}
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string());
@@ -309,10 +320,8 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
// public key if it was not given
if (field_address_found)
{
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, testnet, field_address))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, testnet, field_address))
{
tools::fail_msg_writer() << tools::wallet2::tr("invalid address");
return false;
@@ -324,7 +333,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key");
return false;
}
- if (address.m_view_public_key != pkey) {
+ if (info.address.m_view_public_key != pkey) {
tools::fail_msg_writer() << tools::wallet2::tr("view key does not match standard address");
return false;
}
@@ -336,7 +345,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key");
return false;
}
- if (address.m_spend_public_key != pkey) {
+ if (info.address.m_spend_public_key != pkey) {
tools::fail_msg_writer() << tools::wallet2::tr("spend key does not match standard address");
return false;
}
@@ -373,11 +382,13 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
// from the address
if (field_address_found)
{
- cryptonote::account_public_address address2;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- get_account_integrated_address_from_str(address2, has_payment_id, new_payment_id, testnet, field_address);
- address.m_spend_public_key = address2.m_spend_public_key;
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, testnet, field_address))
+ {
+ tools::fail_msg_writer() << tools::wallet2::tr("failed to parse address: ") << field_address;
+ return false;
+ }
+ address.m_spend_public_key = info.address.m_spend_public_key;
}
wallet->generate(field_filename, field_password, address, viewkey);
}
@@ -419,6 +430,20 @@ static void throw_on_rpc_response_error(const boost::optional<std::string> &stat
THROW_WALLET_EXCEPTION_IF(*status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, method, *status);
}
+std::string strjoin(const std::vector<size_t> &V, const char *sep)
+{
+ std::stringstream ss;
+ bool first = true;
+ for (const auto &v: V)
+ {
+ if (!first)
+ ss << sep;
+ ss << std::to_string(v);
+ first = false;
+ }
+ return ss.str();
+}
+
} //namespace
namespace tools
@@ -500,15 +525,19 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit)
+bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit, bool ssl)
{
+ m_checkpoints.init_default_checkpoints(m_testnet);
if(m_http_client.is_connected())
m_http_client.disconnect();
m_is_initialized = true;
m_upper_transaction_size_limit = upper_transaction_size_limit;
m_daemon_address = std::move(daemon_address);
m_daemon_login = std::move(daemon_login);
- return m_http_client.set_server(get_daemon_address(), get_daemon_login());
+ // When switching from light wallet to full wallet, we need to reset the height we got from lw node.
+ if(m_light_wallet)
+ m_local_bc_height = m_blockchain.size();
+ return m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl);
}
//----------------------------------------------------------------------------------------------------
bool wallet2::is_deterministic() const
@@ -520,7 +549,7 @@ bool wallet2::is_deterministic() const
return keys_deterministic;
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::get_seed(std::string& electrum_words) const
+bool wallet2::get_seed(std::string& electrum_words, const std::string &passphrase) const
{
bool keys_deterministic = is_deterministic();
if (!keys_deterministic)
@@ -534,7 +563,10 @@ bool wallet2::get_seed(std::string& electrum_words) const
return false;
}
- crypto::ElectrumWords::bytes_to_words(get_account().get_keys().m_spend_secret_key, electrum_words, seed_language);
+ crypto::secret_key key = get_account().get_keys().m_spend_secret_key;
+ if (!passphrase.empty())
+ key = cryptonote::encrypt_key(key, passphrase);
+ crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language);
return true;
}
@@ -553,6 +585,129 @@ void wallet2::set_seed_language(const std::string &language)
{
seed_language = language;
}
+//----------------------------------------------------------------------------------------------------
+cryptonote::account_public_address wallet2::get_subaddress(const cryptonote::subaddress_index& index) const
+{
+ const cryptonote::account_keys& keys = m_account.get_keys();
+ if (index.is_zero())
+ return keys.m_account_address;
+
+ crypto::public_key D = get_subaddress_spend_public_key(index);
+
+ // C = a*D
+ crypto::public_key C = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(D), rct::sk2rct(keys.m_view_secret_key))); // could have defined secret_key_mult_public_key() under src/crypto
+
+ // result: (C, D)
+ cryptonote::account_public_address address;
+ address.m_view_public_key = C;
+ address.m_spend_public_key = D;
+ return address;
+}
+//----------------------------------------------------------------------------------------------------
+crypto::public_key wallet2::get_subaddress_spend_public_key(const cryptonote::subaddress_index& index) const
+{
+ const cryptonote::account_keys& keys = m_account.get_keys();
+ if (index.is_zero())
+ return keys.m_account_address.m_spend_public_key;
+
+ // m = Hs(a || index_major || index_minor)
+ crypto::secret_key m = cryptonote::get_subaddress_secret_key(keys.m_view_secret_key, index);
+
+ // M = m*G
+ crypto::public_key M;
+ crypto::secret_key_to_public_key(m, M);
+
+ // D = B + M
+ rct::key D_rct;
+ rct::addKeys(D_rct, rct::pk2rct(keys.m_account_address.m_spend_public_key), rct::pk2rct(M)); // could have defined add_public_key() under src/crypto
+ crypto::public_key D = rct::rct2pk(D_rct);
+
+ return D;
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::get_subaddress_as_str(const cryptonote::subaddress_index& index) const
+{
+ cryptonote::account_public_address address = get_subaddress(index);
+ return cryptonote::get_account_address_as_str(m_testnet, !index.is_zero(), address);
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::get_integrated_address_as_str(const crypto::hash8& payment_id) const
+{
+ return cryptonote::get_account_integrated_address_as_str(m_testnet, get_address(), payment_id);
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::add_subaddress_account(const std::string& label)
+{
+ uint32_t index_major = (uint32_t)get_num_subaddress_accounts();
+ expand_subaddresses({index_major, 0});
+ m_subaddress_labels[index_major][0] = label;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::add_subaddress(uint32_t index_major, const std::string& label)
+{
+ if (index_major >= m_subaddress_labels.size())
+ throw std::runtime_error("index_major is out of bound");
+ uint32_t index_minor = (uint32_t)get_num_subaddresses(index_major);
+ expand_subaddresses({index_major, index_minor});
+ m_subaddress_labels[index_major][index_minor] = label;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index)
+{
+ if (m_subaddress_labels.size() <= index.major)
+ {
+ // add new accounts
+ cryptonote::subaddress_index index2;
+ for (index2.major = m_subaddress_labels.size(); index2.major < index.major + SUBADDRESS_LOOKAHEAD_MAJOR; ++index2.major)
+ {
+ for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor)
+ {
+ if (m_subaddresses_inv.count(index2) == 0)
+ {
+ crypto::public_key D = get_subaddress_spend_public_key(index2);
+ m_subaddresses[D] = index2;
+ m_subaddresses_inv[index2] = D;
+ }
+ }
+ }
+ m_subaddress_labels.resize(index.major + 1, {"Untitled account"});
+ m_subaddress_labels[index.major].resize(index.minor + 1);
+ }
+ else if (m_subaddress_labels[index.major].size() <= index.minor)
+ {
+ // add new subaddresses
+ cryptonote::subaddress_index index2 = index;
+ for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor)
+ {
+ if (m_subaddresses_inv.count(index2) == 0)
+ {
+ crypto::public_key D = get_subaddress_spend_public_key(index2);
+ m_subaddresses[D] = index2;
+ m_subaddresses_inv[index2] = D;
+ }
+ }
+ m_subaddress_labels[index.major].resize(index.minor + 1);
+ }
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::get_subaddress_label(const cryptonote::subaddress_index& index) const
+{
+ if (index.major >= m_subaddress_labels.size() || index.minor >= m_subaddress_labels[index.major].size())
+ {
+ MERROR("Subaddress label doesn't exist");
+ return "";
+ }
+ return m_subaddress_labels[index.major][index.minor];
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, const std::string &label)
+{
+ if (index.major >= m_subaddress_labels.size() || index.minor >= m_subaddress_labels[index.major].size())
+ MERROR("Subaddress index is out of bounds. Failed to set subaddress label.");
+ else
+ m_subaddress_labels[index.major][index.minor] = label;
+}
+//----------------------------------------------------------------------------------------------------
/*!
* \brief Tells if the wallet file is deprecated.
*/
@@ -577,35 +732,28 @@ void wallet2::set_unspent(size_t idx)
td.m_spent_height = 0;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::check_acc_out_precomp(const crypto::public_key &spend_public_key, const tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const
+void wallet2::check_acc_out_precomp(const tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const
{
if (o.target.type() != typeid(txout_to_key))
{
- error = true;
+ tx_scan_info.error = true;
LOG_ERROR("wrong type id in transaction out");
return;
}
- received = is_out_to_acc_precomp(spend_public_key, boost::get<txout_to_key>(o.target), derivation, i);
- if(received)
+ tx_scan_info.received = is_out_to_acc_precomp(m_subaddresses, boost::get<txout_to_key>(o.target).key, derivation, additional_derivations, i);
+ if(tx_scan_info.received)
{
- money_transfered = o.amount; // may be 0 for ringct outputs
+ tx_scan_info.money_transfered = o.amount; // may be 0 for ringct outputs
}
else
{
- money_transfered = 0;
+ tx_scan_info.money_transfered = 0;
}
- error = false;
+ tx_scan_info.error = false;
}
//----------------------------------------------------------------------------------------------------
-static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key &pub, const crypto::secret_key &sec, unsigned int i, rct::key & mask)
+static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &derivation, unsigned int i, rct::key & mask)
{
- crypto::key_derivation derivation;
- bool r = crypto::generate_key_derivation(pub, sec, derivation);
- if (!r)
- {
- LOG_ERROR("Failed to generate key derivation to decode rct output " << i);
- return 0;
- }
crypto::secret_key scalar1;
crypto::derivation_to_scalar(derivation, i, scalar1);
try
@@ -628,11 +776,21 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key &pub,
}
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki)
+void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs)
{
- if (!cryptonote::generate_key_image_helper(ack, tx_public_key, real_output_index, in_ephemeral, ki))
- return false;
- return true;
+ bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki);
+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
+ THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key,
+ error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
+
+ outs.push_back(i);
+ if (tx_scan_info.money_transfered == 0)
+ {
+ tx_scan_info.money_transfered = tools::decodeRct(tx.rct_signatures, tx_scan_info.received->derivation, i, tx_scan_info.mask);
+ }
+ tx_money_got_in_outs[tx_scan_info.received->index] += tx_scan_info.money_transfered;
+ tx_scan_info.amount = tx_scan_info.money_transfered;
+ ++num_vouts_received;
}
//----------------------------------------------------------------------------------------------------
void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool)
@@ -640,10 +798,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
// In this function, tx (probably) only contains the base information
// (that is, the prunable stuff may or may not be included)
- if (!miner_tx)
+ if (!miner_tx && !pool)
process_unconfirmed(txid, tx, height);
std::vector<size_t> outs;
- uint64_t tx_money_got_in_outs = 0;
+ std::unordered_map<cryptonote::subaddress_index, uint64_t> tx_money_got_in_outs; // per receiving subaddress index
crypto::public_key tx_pub_key = null_pkey;
std::vector<tx_extra_field> tx_extra_fields;
@@ -655,6 +813,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
// Don't try to extract tx public key if tx has no ouputs
size_t pk_index = 0;
+ std::vector<tx_scan_info_t> tx_scan_info(tx.vout.size());
while (!tx.vout.empty())
{
// if tx.vout is not empty, we loop through all tx pubkeys
@@ -673,121 +832,84 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
int num_vouts_received = 0;
tx_pub_key = pub_key_field.pub_key;
bool r = true;
- std::deque<cryptonote::keypair> in_ephemeral(tx.vout.size());
- std::deque<crypto::key_image> ki(tx.vout.size());
- std::deque<uint64_t> amount(tx.vout.size());
- std::deque<rct::key> mask(tx.vout.size());
tools::threadpool& tpool = tools::threadpool::getInstance();
tools::threadpool::waiter waiter;
- int threads = tpool.get_max_concurrency();
const cryptonote::account_keys& keys = m_account.get_keys();
crypto::key_derivation derivation;
generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation);
+
+ // additional tx pubkeys and derivations for multi-destination transfers involving one or more subaddresses
+ std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
+ std::vector<crypto::key_derivation> additional_derivations;
+ for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
+ {
+ additional_derivations.push_back({});
+ generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back());
+ }
+
if (miner_tx && m_refresh_type == RefreshNoCoinbase)
{
// assume coinbase isn't for us
}
else if (miner_tx && m_refresh_type == RefreshOptimizeCoinbase)
{
- uint64_t money_transfered = 0;
- bool error = false, received = false;
- check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[0], derivation, 0, received, money_transfered, error);
- if (error)
+ check_acc_out_precomp(tx.vout[0], derivation, additional_derivations, 0, tx_scan_info[0]);
+ if (tx_scan_info[0].error)
{
r = false;
}
else
{
// this assumes that the miner tx pays a single address
- if (received)
+ if (tx_scan_info[0].received)
{
- wallet_generate_key_image_helper(keys, tx_pub_key, 0, in_ephemeral[0], ki[0]);
- THROW_WALLET_EXCEPTION_IF(in_ephemeral[0].pub != boost::get<cryptonote::txout_to_key>(tx.vout[0].target).key,
- error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
-
- outs.push_back(0);
- if (money_transfered == 0)
- {
- money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, 0, mask[0]);
- }
- amount[0] = money_transfered;
- tx_money_got_in_outs = money_transfered;
- ++num_vouts_received;
+ scan_output(keys, tx, tx_pub_key, 0, tx_scan_info[0], num_vouts_received, tx_money_got_in_outs, outs);
// process the other outs from that tx
-
- std::vector<uint64_t> money_transfered(tx.vout.size());
- std::deque<bool> error(tx.vout.size());
- std::deque<bool> received(tx.vout.size());
// the first one was already checked
for (size_t i = 1; i < tx.vout.size(); ++i)
{
- tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i,
- std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i])));
+ tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i,
+ std::ref(tx_scan_info[i])));
}
waiter.wait();
+
for (size_t i = 1; i < tx.vout.size(); ++i)
{
- if (error[i])
+ if (tx_scan_info[i].error)
{
r = false;
break;
}
- if (received[i])
+ if (tx_scan_info[i].received)
{
- wallet_generate_key_image_helper(keys, tx_pub_key, i, in_ephemeral[i], ki[i]);
- THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key,
- error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
-
- outs.push_back(i);
- if (money_transfered[i] == 0)
- {
- money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]);
- }
- tx_money_got_in_outs += money_transfered[i];
- amount[i] = money_transfered[i];
- ++num_vouts_received;
+ scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs);
}
}
}
}
}
- else if (tx.vout.size() > 1 && threads > 1)
+ else if (tx.vout.size() > 1 && tools::threadpool::getInstance().get_max_concurrency() > 1)
{
tools::threadpool& tpool = tools::threadpool::getInstance();
tools::threadpool::waiter waiter;
- std::vector<uint64_t> money_transfered(tx.vout.size());
- std::deque<bool> error(tx.vout.size());
- std::deque<bool> received(tx.vout.size());
for (size_t i = 0; i < tx.vout.size(); ++i)
{
- tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i,
- std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i])));
+ tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i,
+ std::ref(tx_scan_info[i])));
}
waiter.wait();
- tx_money_got_in_outs = 0;
for (size_t i = 0; i < tx.vout.size(); ++i)
{
- if (error[i])
+ if (tx_scan_info[i].error)
{
r = false;
break;
}
- if (received[i])
+ if (tx_scan_info[i].received)
{
- wallet_generate_key_image_helper(keys, tx_pub_key, i, in_ephemeral[i], ki[i]);
- THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key,
- error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
-
- outs.push_back(i);
- if (money_transfered[i] == 0)
- {
- money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]);
- }
- tx_money_got_in_outs += money_transfered[i];
- amount[i] = money_transfered[i];
- ++num_vouts_received;
+ scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs);
}
}
}
@@ -795,31 +917,15 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
{
for (size_t i = 0; i < tx.vout.size(); ++i)
{
- uint64_t money_transfered = 0;
- bool error = false, received = false;
- check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[i], derivation, i, received, money_transfered, error);
- if (error)
+ check_acc_out_precomp(tx.vout[i], derivation, additional_derivations, i, tx_scan_info[i]);
+ if (tx_scan_info[i].error)
{
r = false;
break;
}
- else
+ if (tx_scan_info[i].received)
{
- if (received)
- {
- wallet_generate_key_image_helper(keys, tx_pub_key, i, in_ephemeral[i], ki[i]);
- THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key,
- error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
-
- outs.push_back(i);
- if (money_transfered == 0)
- {
- money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]);
- }
- amount[i] = money_transfered;
- tx_money_got_in_outs += money_transfered;
- ++num_vouts_received;
- }
+ scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs);
}
}
}
@@ -841,7 +947,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
THROW_WALLET_EXCEPTION_IF(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" +
std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size()));
- auto kit = m_pub_keys.find(in_ephemeral[o].pub);
+ auto kit = m_pub_keys.find(tx_scan_info[o].in_ephemeral.pub);
THROW_WALLET_EXCEPTION_IF(kit != m_pub_keys.end() && kit->second >= m_transfers.size(),
error::wallet_internal_error, std::string("Unexpected transfer index from public key: ")
+ "got " + (kit == m_pub_keys.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second))
@@ -857,14 +963,16 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_global_output_index = o_indices[o];
td.m_tx = (const cryptonote::transaction_prefix&)tx;
td.m_txid = txid;
- td.m_key_image = ki[o];
+ td.m_key_image = tx_scan_info[o].ki;
td.m_key_image_known = !m_watch_only;
td.m_amount = tx.vout[o].amount;
td.m_pk_index = pk_index - 1;
+ td.m_subaddr_index = tx_scan_info[o].received->index;
+ expand_subaddresses(tx_scan_info[o].received->index);
if (td.m_amount == 0)
{
- td.m_mask = mask[o];
- td.m_amount = amount[o];
+ td.m_mask = tx_scan_info[o].mask;
+ td.m_amount = tx_scan_info[o].amount;
td.m_rct = true;
}
else if (miner_tx && tx.version == 2)
@@ -879,10 +987,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
}
set_unspent(m_transfers.size()-1);
m_key_images[td.m_key_image] = m_transfers.size()-1;
- m_pub_keys[in_ephemeral[o].pub] = m_transfers.size()-1;
+ m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1;
LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid);
if (0 != m_callback)
- m_callback->on_money_received(height, txid, tx, td.m_amount);
+ m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index);
}
}
else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount)
@@ -898,7 +1006,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
<< " from received " << print_money(tx.vout[o].amount) << " output already exists with "
<< print_money(m_transfers[kit->second].amount()) << ", replacing with new output");
// The new larger output replaced a previous smaller one
- tx_money_got_in_outs -= tx.vout[o].amount;
+ tx_money_got_in_outs[tx_scan_info[o].received->index] -= tx.vout[o].amount;
if (!pool)
{
@@ -910,10 +1018,12 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_txid = txid;
td.m_amount = tx.vout[o].amount;
td.m_pk_index = pk_index - 1;
+ td.m_subaddr_index = tx_scan_info[o].received->index;
+ expand_subaddresses(tx_scan_info[o].received->index);
if (td.m_amount == 0)
{
- td.m_mask = mask[o];
- td.m_amount = amount[o];
+ td.m_mask = tx_scan_info[o].mask;
+ td.m_amount = tx_scan_info[o].amount;
td.m_rct = true;
}
else if (miner_tx && tx.version == 2)
@@ -926,12 +1036,12 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_mask = rct::identity();
td.m_rct = false;
}
- THROW_WALLET_EXCEPTION_IF(td.get_public_key() != in_ephemeral[o].pub, error::wallet_internal_error, "Inconsistent public keys");
+ THROW_WALLET_EXCEPTION_IF(td.get_public_key() != tx_scan_info[o].in_ephemeral.pub, error::wallet_internal_error, "Inconsistent public keys");
THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status");
LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid);
if (0 != m_callback)
- m_callback->on_money_received(height, txid, tx, td.m_amount);
+ m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index);
}
}
}
@@ -939,6 +1049,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
}
uint64_t tx_money_spent_in_ins = 0;
+ boost::optional<uint32_t> subaddr_account;
+ std::set<uint32_t> subaddr_indices;
// check all outputs for spending (compare key images)
for(auto& in: tx.vin)
{
@@ -957,23 +1069,50 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
}
amount = td.amount();
tx_money_spent_in_ins += amount;
+ if (subaddr_account && *subaddr_account != td.m_subaddr_index.major)
+ LOG_ERROR("spent funds are from different subaddress accounts; count of incoming/outgoing payments will be incorrect");
+ subaddr_account = td.m_subaddr_index.major;
+ subaddr_indices.insert(td.m_subaddr_index.minor);
if (!pool)
{
LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid);
set_spent(it->second, height);
if (0 != m_callback)
- m_callback->on_money_spent(height, txid, tx, amount, tx);
+ m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index);
}
}
}
- if (tx_money_spent_in_ins > 0)
+ if (tx_money_spent_in_ins > 0 && !pool)
{
- process_outgoing(txid, tx, height, ts, tx_money_spent_in_ins, tx_money_got_in_outs);
+ uint64_t self_received = std::accumulate<decltype(tx_money_got_in_outs.begin()), uint64_t>(tx_money_got_in_outs.begin(), tx_money_got_in_outs.end(), 0,
+ [&subaddr_account] (uint64_t acc, const std::pair<cryptonote::subaddress_index, uint64_t>& p)
+ {
+ return acc + (p.first.major == *subaddr_account ? p.second : 0);
+ });
+ process_outgoing(txid, tx, height, ts, tx_money_spent_in_ins, self_received, *subaddr_account, subaddr_indices);
+ // if sending to yourself at the same subaddress account, set the outgoing payment amount to 0 so that it's less confusing
+ uint64_t fee = tx.version == 1 ? tx_money_spent_in_ins - get_outs_money_amount(tx) : tx.rct_signatures.txnFee;
+ if (tx_money_spent_in_ins == self_received + fee)
+ {
+ auto i = m_confirmed_txs.find(txid);
+ THROW_WALLET_EXCEPTION_IF(i == m_confirmed_txs.end(), error::wallet_internal_error,
+ "confirmed tx wasn't found: " + string_tools::pod_to_hex(txid));
+ i->second.m_change = self_received;
+ }
+ }
+
+ // remove change sent to the spending subaddress account from the list of received funds
+ for (auto i = tx_money_got_in_outs.begin(); i != tx_money_got_in_outs.end();)
+ {
+ if (subaddr_account && i->first.major == *subaddr_account)
+ i = tx_money_got_in_outs.erase(i);
+ else
+ ++i;
}
- uint64_t received = (tx_money_spent_in_ins < tx_money_got_in_outs) ? tx_money_got_in_outs - tx_money_spent_in_ins : 0;
- if (0 < received)
+ // create payment_details for each incoming transfer to a subaddress index
+ if (tx_money_got_in_outs.size() > 0)
{
tx_extra_nonce extra_nonce;
crypto::hash payment_id = null_hash;
@@ -1014,20 +1153,24 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
LOG_PRINT_L2("Found unencrypted payment ID: " << payment_id);
}
- payment_details payment;
- payment.m_tx_hash = txid;
- payment.m_amount = received;
- payment.m_block_height = height;
- payment.m_unlock_time = tx.unlock_time;
- payment.m_timestamp = ts;
- if (pool) {
- m_unconfirmed_payments.emplace(payment_id, payment);
- if (0 != m_callback)
- m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount);
+ for (const auto& i : tx_money_got_in_outs)
+ {
+ payment_details payment;
+ payment.m_tx_hash = txid;
+ payment.m_amount = i.second;
+ payment.m_block_height = height;
+ payment.m_unlock_time = tx.unlock_time;
+ payment.m_timestamp = ts;
+ payment.m_subaddr_index = i.first;
+ if (pool) {
+ m_unconfirmed_payments.emplace(payment_id, payment);
+ if (0 != m_callback)
+ m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount, payment.m_subaddr_index);
+ }
+ else
+ m_payments.emplace(payment_id, payment);
+ LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount);
}
- else
- m_payments.emplace(payment_id, payment);
- LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount);
}
}
//----------------------------------------------------------------------------------------------------
@@ -1051,7 +1194,7 @@ void wallet2::process_unconfirmed(const crypto::hash &txid, const cryptonote::tr
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received)
+void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices)
{
std::pair<std::unordered_map<crypto::hash, confirmed_transfer_details>::iterator, bool> entry = m_confirmed_txs.insert(std::make_pair(txid, confirmed_transfer_details()));
// fill with the info we know, some info might already be there
@@ -1077,6 +1220,8 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans
get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id);
}
}
+ entry.first->second.m_subaddr_account = subaddr_account;
+ entry.first->second.m_subaddr_indices = subaddr_indices;
}
entry.first->second.m_block_height = height;
entry.first->second.m_timestamp = ts;
@@ -1128,16 +1273,19 @@ void wallet2::get_short_chain_history(std::list<crypto::hash>& ids) const
{
size_t i = 0;
size_t current_multiplier = 1;
- size_t sz = m_blockchain.size();
+ size_t sz = m_blockchain.size() - m_blockchain.offset();
if(!sz)
+ {
+ ids.push_back(m_blockchain.genesis());
return;
+ }
size_t current_back_offset = 1;
- bool genesis_included = false;
+ bool base_included = false;
while(current_back_offset < sz)
{
- ids.push_back(m_blockchain[sz-current_back_offset]);
+ ids.push_back(m_blockchain[m_blockchain.offset() + sz-current_back_offset]);
if(sz-current_back_offset == 0)
- genesis_included = true;
+ base_included = true;
if(i < 10)
{
++current_back_offset;
@@ -1147,8 +1295,10 @@ void wallet2::get_short_chain_history(std::list<crypto::hash>& ids) const
}
++i;
}
- if(!genesis_included)
- ids.push_back(m_blockchain[0]);
+ if(!base_included)
+ ids.push_back(m_blockchain[m_blockchain.offset()]);
+ if(m_blockchain.offset())
+ ids.push_back(m_blockchain.genesis());
}
//----------------------------------------------------------------------------------------------------
void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const
@@ -1233,6 +1383,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote::
size_t tx_o_indices_idx = 0;
THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "size mismatch");
+ THROW_WALLET_EXCEPTION_IF(!m_blockchain.is_in_bounds(current_index), error::wallet_internal_error, "Index out of bounds of hashchain");
tools::threadpool& tpool = tools::threadpool::getInstance();
int threads = tpool.get_max_concurrency();
@@ -1365,6 +1516,34 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei
error = true;
}
}
+
+void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes)
+{
+ // remove pool txes to us that aren't in the pool anymore
+ std::unordered_multimap<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin();
+ while (uit != m_unconfirmed_payments.end())
+ {
+ const crypto::hash &txid = uit->second.m_tx_hash;
+ bool found = false;
+ for (const auto &it2: tx_hashes)
+ {
+ if (it2 == txid)
+ {
+ found = true;
+ break;
+ }
+ }
+ auto pit = uit++;
+ if (!found)
+ {
+ MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool");
+ m_unconfirmed_payments.erase(pit);
+ if (0 != m_callback)
+ m_callback->on_pool_tx_removed(txid);
+ }
+ }
+}
+
//----------------------------------------------------------------------------------------------------
void wallet2::update_pool_state(bool refreshed)
{
@@ -1442,28 +1621,8 @@ void wallet2::update_pool_state(bool refreshed)
// the in transfers list instead (or nowhere if it just
// disappeared without being mined)
if (refreshed)
- {
- std::unordered_map<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin();
- while (uit != m_unconfirmed_payments.end())
- {
- const crypto::hash &txid = uit->second.m_tx_hash;
- bool found = false;
- for (const auto &it2: res.tx_hashes)
- {
- if (it2 == txid)
- {
- found = true;
- break;
- }
- }
- auto pit = uit++;
- if (!found)
- {
- MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool");
- m_unconfirmed_payments.erase(pit);
- }
- }
- }
+ remove_obsolete_pool_txs(res.tx_hashes);
+
MDEBUG("update_pool_state done second loop");
// gather txids of new pool txes to us
@@ -1493,6 +1652,18 @@ void wallet2::update_pool_state(bool refreshed)
if (i.first == txid)
{
found = true;
+ // if this is a payment to yourself at a different subaddress account, don't skip it
+ // so that you can see the incoming pool tx with 'show_transfers' on that receiving subaddress account
+ const unconfirmed_transfer_details& utd = i.second;
+ for (const auto& dst : utd.m_dests)
+ {
+ auto subaddr_index = m_subaddresses.find(dst.addr.m_spend_public_key);
+ if (subaddr_index != m_subaddresses.end() && subaddr_index->second.major != utd.m_subaddr_account)
+ {
+ found = false;
+ break;
+ }
+ }
break;
}
}
@@ -1529,23 +1700,22 @@ void wallet2::update_pool_state(bool refreshed)
{
if (res.txs.size() == txids.size())
{
- size_t n = 0;
- for (const auto &txid: txids)
+ for (const auto &tx_entry: res.txs)
{
- // might have just been put in a block
- if (res.txs[n].in_pool)
+ if (tx_entry.in_pool)
{
cryptonote::transaction tx;
cryptonote::blobdata bd;
crypto::hash tx_hash, tx_prefix_hash;
- if (epee::string_tools::parse_hexstr_to_binbuff(res.txs[n].as_hex, bd))
+ if (epee::string_tools::parse_hexstr_to_binbuff(tx_entry.as_hex, bd))
{
if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash))
{
- if (tx_hash == txid)
+ const std::vector<crypto::hash>::const_iterator i = std::find(txids.begin(), txids.end(), tx_hash);
+ if (i != txids.end())
{
- process_new_transaction(txid, tx, std::vector<uint64_t>(), 0, time(NULL), false, true);
- m_scanned_pool_txs[0].insert(txid);
+ process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true);
+ m_scanned_pool_txs[0].insert(tx_hash);
if (m_scanned_pool_txs[0].size() > 5000)
{
std::swap(m_scanned_pool_txs[0], m_scanned_pool_txs[1]);
@@ -1554,7 +1724,7 @@ void wallet2::update_pool_state(bool refreshed)
}
else
{
- LOG_PRINT_L0("Mismatched txids when processing unconfimed txes from pool");
+ MERROR("Got txid " << tx_hash << " which we did not ask for");
}
}
else
@@ -1564,14 +1734,13 @@ void wallet2::update_pool_state(bool refreshed)
}
else
{
- LOG_PRINT_L0("Failed to parse tx " << txid);
+ LOG_PRINT_L0("Failed to parse transaction from daemon");
}
}
else
{
- LOG_PRINT_L1("Tx " << txid << " was in pool, but is no more");
+ LOG_PRINT_L1("Transaction from daemon was in pool, but is no more");
}
- ++n;
}
}
else
@@ -1643,12 +1812,13 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height,
}
-bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description)
+bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress)
{
wallet2::address_book_row a;
a.m_address = address;
a.m_payment_id = payment_id;
a.m_description = description;
+ a.m_is_subaddress = is_subaddress;
auto old_size = m_address_book.size();
m_address_book.push_back(a);
@@ -1669,6 +1839,39 @@ bool wallet2::delete_address_book_row(std::size_t row_id) {
//----------------------------------------------------------------------------------------------------
void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& received_money)
{
+ if(m_light_wallet) {
+
+ // MyMonero get_address_info needs to be called occasionally to trigger wallet sync.
+ // This call is not really needed for other purposes and can be removed if mymonero changes their backend.
+ cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response res;
+
+ // Get basic info
+ if(light_wallet_get_address_info(res)) {
+ // Last stored block height
+ uint64_t prev_height = m_light_wallet_blockchain_height;
+ // Update lw heights
+ m_light_wallet_scanned_block_height = res.scanned_block_height;
+ m_light_wallet_blockchain_height = res.blockchain_height;
+ m_local_bc_height = res.blockchain_height;
+ // If new height - call new_block callback
+ if(m_light_wallet_blockchain_height != prev_height)
+ {
+ MDEBUG("new block since last time!");
+ m_callback->on_lw_new_block(m_light_wallet_blockchain_height - 1);
+ }
+ m_light_wallet_connected = true;
+ MDEBUG("lw scanned block height: " << m_light_wallet_scanned_block_height);
+ MDEBUG("lw blockchain height: " << m_light_wallet_blockchain_height);
+ MDEBUG(m_light_wallet_blockchain_height-m_light_wallet_scanned_block_height << " blocks behind");
+ // TODO: add wallet created block info
+
+ light_wallet_get_address_txs();
+ } else
+ m_light_wallet_connected = false;
+
+ // Lighwallet refresh done
+ return;
+ }
received_money = false;
blocks_fetched = 0;
uint64_t added_blocks = 0;
@@ -1767,7 +1970,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re
LOG_PRINT_L1("Failed to check pending transactions");
}
- LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance: " << print_money(balance()) << ", unlocked: " << print_money(unlocked_balance()));
+ LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all()) << ", unlocked: " << print_money(unlocked_balance_all()));
}
//----------------------------------------------------------------------------------------------------
bool wallet2::refresh(uint64_t & blocks_fetched, bool& received_money, bool& ok)
@@ -1787,6 +1990,13 @@ bool wallet2::refresh(uint64_t & blocks_fetched, bool& received_money, bool& ok)
void wallet2::detach_blockchain(uint64_t height)
{
LOG_PRINT_L0("Detaching blockchain on height " << height);
+
+ // size 1 2 3 4 5 6 7 8 9
+ // block 0 1 2 3 4 5 6 7 8
+ // C
+ THROW_WALLET_EXCEPTION_IF(height <= m_checkpoints.get_max_height() && m_blockchain.size() > m_checkpoints.get_max_height(),
+ error::wallet_internal_error, "Daemon claims reorg below last checkpoint");
+
size_t transfers_detached = 0;
for (size_t i = 0; i < m_transfers.size(); ++i)
@@ -1817,8 +2027,8 @@ void wallet2::detach_blockchain(uint64_t height)
}
m_transfers.erase(it, m_transfers.end());
- size_t blocks_detached = m_blockchain.end() - (m_blockchain.begin()+height);
- m_blockchain.erase(m_blockchain.begin()+height, m_blockchain.end());
+ size_t blocks_detached = m_blockchain.size() - height;
+ m_blockchain.crop(height);
m_local_bc_height -= blocks_detached;
for (auto it = m_payments.begin(); it != m_payments.end(); )
@@ -1855,12 +2065,16 @@ bool wallet2::clear()
m_unconfirmed_txs.clear();
m_payments.clear();
m_tx_keys.clear();
+ m_additional_tx_keys.clear();
m_confirmed_txs.clear();
m_unconfirmed_payments.clear();
m_scanned_pool_txs[0].clear();
m_scanned_pool_txs[1].clear();
m_address_book.clear();
m_local_bc_height = 1;
+ m_subaddresses.clear();
+ m_subaddresses_inv.clear();
+ m_subaddress_labels.clear();
return true;
}
@@ -1943,6 +2157,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p
value2.SetInt(m_confirm_backlog ? 1 :0);
json.AddMember("confirm_backlog", value2, json.GetAllocator());
+ value2.SetUint(m_confirm_backlog_threshold);
+ json.AddMember("confirm_backlog_threshold", value2, json.GetAllocator());
+
value2.SetInt(m_testnet ? 1 :0);
json.AddMember("testnet", value2, json.GetAllocator());
@@ -2018,6 +2235,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
m_min_output_value = 0;
m_merge_destinations = false;
m_confirm_backlog = true;
+ m_confirm_backlog_threshold = 0;
}
else
{
@@ -2090,6 +2308,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
m_merge_destinations = field_merge_destinations;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_backlog, int, Int, false, true);
m_confirm_backlog = field_confirm_backlog;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_backlog_threshold, uint32_t, Uint, false, 0);
+ m_confirm_backlog_threshold = field_confirm_backlog_threshold;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, testnet, int, Int, false, m_testnet);
// Wallet is being opened with testnet flag, but is saved as a mainnet wallet
THROW_WALLET_EXCEPTION_IF(m_testnet && !field_testnet, error::wallet_internal_error, "Mainnet wallet can not be opened as testnet wallet");
@@ -2227,14 +2447,6 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const std::stri
m_refresh_from_block_height = height >= blocks_per_month ? height - blocks_per_month : 0;
}
- if(m_refresh_from_block_height == 0 && !recover){
- // Wallets created offline don't know blockchain height.
- // Set blockchain height calculated from current date/time
- uint64_t approx_blockchain_height = get_approximate_blockchain_height();
- if(approx_blockchain_height > 0) {
- m_refresh_from_block_height = approx_blockchain_height - blocks_per_month;
- }
- }
bool r = store_keys(m_keys_file, password, false);
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
@@ -2244,6 +2456,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const std::stri
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
+ add_subaddress_account(tr("Primary account"));
store();
return retval;
@@ -2279,6 +2492,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password,
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
+ add_subaddress_account(tr("Primary account"));
store();
}
@@ -2314,6 +2528,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password,
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
+ add_subaddress_account(tr("Primary account"));
store();
}
@@ -2424,6 +2639,12 @@ bool wallet2::check_connection(uint32_t *version, uint32_t timeout)
boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex);
+ // TODO: Add light wallet version check.
+ if(m_light_wallet) {
+ version = 0;
+ return m_light_wallet_connected;
+ }
+
if(!m_http_client.is_connected())
{
m_node_rpc_proxy.invalidate();
@@ -2562,13 +2783,52 @@ void wallet2::load(const std::string& wallet_, const std::string& password)
check_genesis(genesis_hash);
}
+ trim_hashchain();
+
+ if (get_num_subaddress_accounts() == 0)
+ add_subaddress_account(tr("Primary account"));
+
m_local_bc_height = m_blockchain.size();
}
//----------------------------------------------------------------------------------------------------
+void wallet2::trim_hashchain()
+{
+ uint64_t height = m_checkpoints.get_max_height();
+ if (!m_blockchain.empty() && m_blockchain.size() == m_blockchain.offset())
+ {
+ MINFO("Fixing empty hashchain");
+ epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request> req = AUTO_VAL_INIT(req);
+ epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response, std::string> res = AUTO_VAL_INIT(res);
+ m_daemon_rpc_mutex.lock();
+ req.jsonrpc = "2.0";
+ req.id = epee::serialization::storage_entry(0);
+ req.method = "getblockheaderbyheight";
+ req.params.height = m_blockchain.size() - 1;
+ bool r = net_utils::invoke_http_json("/json_rpc", req, res, m_http_client, rpc_timeout);
+ m_daemon_rpc_mutex.unlock();
+ if (r && res.result.status == CORE_RPC_STATUS_OK)
+ {
+ crypto::hash hash;
+ epee::string_tools::hex_to_pod(res.result.block_header.hash, hash);
+ m_blockchain.refill(hash);
+ }
+ else
+ {
+ MERROR("Failed to request block header from daemon, hash chain may be unable to sync till the wallet is loaded with a usable daemon");
+ }
+ }
+ if (height > 0 && m_blockchain.size() > height)
+ {
+ --height;
+ MDEBUG("trimming to " << height << ", offset " << m_blockchain.offset());
+ m_blockchain.trim(height);
+ }
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::check_genesis(const crypto::hash& genesis_hash) const {
std::string what("Genesis block mismatch. You probably use wallet without testnet flag with blockchain from test network or vice versa");
- THROW_WALLET_EXCEPTION_IF(genesis_hash != m_blockchain[0], error::wallet_internal_error, what);
+ THROW_WALLET_EXCEPTION_IF(genesis_hash != m_blockchain.genesis(), error::wallet_internal_error, what);
}
//----------------------------------------------------------------------------------------------------
std::string wallet2::path() const
@@ -2583,6 +2843,8 @@ void wallet2::store()
//----------------------------------------------------------------------------------------------------
void wallet2::store_to(const std::string &path, const std::string &password)
{
+ trim_hashchain();
+
// if file is the same, we do:
// 1. save wallet to the *.new file
// 2. remove old wallet file
@@ -2645,10 +2907,11 @@ void wallet2::store_to(const std::string &path, const std::string &password)
// if we here, main wallet file is saved and we only need to save keys and address files
if (!same_file) {
prepare_file_names(path);
- store_keys(m_keys_file, password, false);
+ bool r = store_keys(m_keys_file, password, false);
+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
// save address to the new file
const std::string address_file = m_wallet_file + ".address.txt";
- bool r = file_io_utils::save_string_to_file(address_file, m_account.get_public_address_str(m_testnet));
+ r = file_io_utils::save_string_to_file(address_file, m_account.get_public_address_str(m_testnet));
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_wallet_file);
// remove old wallet file
r = boost::filesystem::remove(old_file);
@@ -2672,52 +2935,113 @@ void wallet2::store_to(const std::string &path, const std::string &password)
}
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::unlocked_balance() const
+uint64_t wallet2::balance(uint32_t index_major) const
{
uint64_t amount = 0;
- for(const transfer_details& td: m_transfers)
- if(!td.m_spent && is_transfer_unlocked(td))
- amount += td.amount();
-
+ if(m_light_wallet)
+ return m_light_wallet_unlocked_balance;
+ for (const auto& i : balance_per_subaddress(index_major))
+ amount += i.second;
return amount;
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::balance() const
+uint64_t wallet2::unlocked_balance(uint32_t index_major) const
{
uint64_t amount = 0;
- for(auto& td: m_transfers)
- if(!td.m_spent)
- amount += td.amount();
-
-
- for(auto& utx: m_unconfirmed_txs)
- if (utx.second.m_state != wallet2::unconfirmed_transfer_details::failed)
- amount+= utx.second.m_change;
-
+ if(m_light_wallet)
+ return m_light_wallet_balance;
+ for (const auto& i : unlocked_balance_per_subaddress(index_major))
+ amount += i.second;
return amount;
}
//----------------------------------------------------------------------------------------------------
+std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_major) const
+{
+ std::map<uint32_t, uint64_t> amount_per_subaddr;
+ for (const auto& td: m_transfers)
+ {
+ if (td.m_subaddr_index.major == index_major && !td.m_spent)
+ {
+ auto found = amount_per_subaddr.find(td.m_subaddr_index.minor);
+ if (found == amount_per_subaddr.end())
+ amount_per_subaddr[td.m_subaddr_index.minor] = td.amount();
+ else
+ found->second += td.amount();
+ }
+ }
+ for (const auto& utx: m_unconfirmed_txs)
+ {
+ if (utx.second.m_subaddr_account == index_major && utx.second.m_state != wallet2::unconfirmed_transfer_details::failed)
+ {
+ // all changes go to 0-th subaddress (in the current subaddress account)
+ auto found = amount_per_subaddr.find(0);
+ if (found == amount_per_subaddr.end())
+ amount_per_subaddr[0] = utx.second.m_change;
+ else
+ found->second += utx.second.m_change;
+ }
+ }
+ return amount_per_subaddr;
+}
+//----------------------------------------------------------------------------------------------------
+std::map<uint32_t, uint64_t> wallet2::unlocked_balance_per_subaddress(uint32_t index_major) const
+{
+ std::map<uint32_t, uint64_t> amount_per_subaddr;
+ for(const transfer_details& td: m_transfers)
+ {
+ if(td.m_subaddr_index.major == index_major && !td.m_spent && is_transfer_unlocked(td))
+ {
+ auto found = amount_per_subaddr.find(td.m_subaddr_index.minor);
+ if (found == amount_per_subaddr.end())
+ amount_per_subaddr[td.m_subaddr_index.minor] = td.amount();
+ else
+ found->second += td.amount();
+ }
+ }
+ return amount_per_subaddr;
+}
+//----------------------------------------------------------------------------------------------------
+uint64_t wallet2::balance_all() const
+{
+ uint64_t r = 0;
+ for (uint32_t index_major = 0; index_major < get_num_subaddress_accounts(); ++index_major)
+ r += balance(index_major);
+ return r;
+}
+//----------------------------------------------------------------------------------------------------
+uint64_t wallet2::unlocked_balance_all() const
+{
+ uint64_t r = 0;
+ for (uint32_t index_major = 0; index_major < get_num_subaddress_accounts(); ++index_major)
+ r += unlocked_balance(index_major);
+ return r;
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::get_transfers(wallet2::transfer_container& incoming_transfers) const
{
incoming_transfers = m_transfers;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height) const
+void wallet2::get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
auto range = m_payments.equal_range(payment_id);
- std::for_each(range.first, range.second, [&payments, &min_height](const payment_container::value_type& x) {
- if (min_height < x.second.m_block_height)
+ std::for_each(range.first, range.second, [&payments, &min_height, &subaddr_account, &subaddr_indices](const payment_container::value_type& x) {
+ if (min_height < x.second.m_block_height &&
+ (!subaddr_account || *subaddr_account == x.second.m_subaddr_index.major) &&
+ (subaddr_indices.empty() || subaddr_indices.count(x.second.m_subaddr_index.minor) == 1))
{
payments.push_back(x.second);
}
});
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height) const
+void wallet2::get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
auto range = std::make_pair(m_payments.begin(), m_payments.end());
- std::for_each(range.first, range.second, [&payments, &min_height, &max_height](const payment_container::value_type& x) {
- if (min_height < x.second.m_block_height && max_height >= x.second.m_block_height)
+ std::for_each(range.first, range.second, [&payments, &min_height, &max_height, &subaddr_account, &subaddr_indices](const payment_container::value_type& x) {
+ if (min_height < x.second.m_block_height && max_height >= x.second.m_block_height &&
+ (!subaddr_account || *subaddr_account == x.second.m_subaddr_index.major) &&
+ (subaddr_indices.empty() || subaddr_indices.count(x.second.m_subaddr_index.minor) == 1))
{
payments.push_back(x);
}
@@ -2725,25 +3049,35 @@ void wallet2::get_payments(std::list<std::pair<crypto::hash,wallet2::payment_det
}
//----------------------------------------------------------------------------------------------------
void wallet2::get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments,
- uint64_t min_height, uint64_t max_height) const
+ uint64_t min_height, uint64_t max_height, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
for (auto i = m_confirmed_txs.begin(); i != m_confirmed_txs.end(); ++i) {
- if (i->second.m_block_height > min_height && i->second.m_block_height <= max_height) {
- confirmed_payments.push_back(*i);
- }
+ if (i->second.m_block_height <= min_height || i->second.m_block_height > max_height)
+ continue;
+ if (subaddr_account && *subaddr_account != i->second.m_subaddr_account)
+ continue;
+ if (!subaddr_indices.empty() && std::count_if(i->second.m_subaddr_indices.begin(), i->second.m_subaddr_indices.end(), [&subaddr_indices](uint32_t index) { return subaddr_indices.count(index) == 1; }) == 0)
+ continue;
+ confirmed_payments.push_back(*i);
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments) const
+void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
for (auto i = m_unconfirmed_txs.begin(); i != m_unconfirmed_txs.end(); ++i) {
+ if (subaddr_account && *subaddr_account != i->second.m_subaddr_account)
+ continue;
+ if (!subaddr_indices.empty() && std::count_if(i->second.m_subaddr_indices.begin(), i->second.m_subaddr_indices.end(), [&subaddr_indices](uint32_t index) { return subaddr_indices.count(index) == 1; }) == 0)
+ continue;
unconfirmed_payments.push_back(*i);
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments) const
+void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) {
+ if ((!subaddr_account || *subaddr_account == i->second.m_subaddr_index.major) &&
+ (subaddr_indices.empty() || subaddr_indices.count(i->second.m_subaddr_index.minor) == 1))
unconfirmed_payments.push_back(*i);
}
}
@@ -2808,6 +3142,7 @@ void wallet2::rescan_blockchain(bool refresh)
generate_genesis(genesis);
crypto::hash genesis_hash = get_block_hash(genesis);
m_blockchain.push_back(genesis_hash);
+ add_subaddress_account(tr("Primary account"));
m_local_bc_height = 1;
if (refresh)
@@ -2816,10 +3151,15 @@ void wallet2::rescan_blockchain(bool refresh)
//----------------------------------------------------------------------------------------------------
bool wallet2::is_transfer_unlocked(const transfer_details& td) const
{
- if(!is_tx_spendtime_unlocked(td.m_tx.unlock_time, td.m_block_height))
+ return is_transfer_unlocked(td.m_tx.unlock_time, td.m_block_height);
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const
+{
+ if(!is_tx_spendtime_unlocked(unlock_time, block_height))
return false;
- if(td.m_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > m_blockchain.size())
+ if(block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > m_local_bc_height)
return false;
return true;
@@ -2830,7 +3170,7 @@ bool wallet2::is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_heig
if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER)
{
//interpret as block index
- if(m_blockchain.size()-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time)
+ if(m_local_bc_height-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time)
return true;
else
return false;
@@ -3005,7 +3345,7 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> un
return found_money;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount)
+void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices)
{
unconfirmed_transfer_details& utd = m_unconfirmed_txs[cryptonote::get_transaction_hash(tx)];
utd.m_amount_in = amount_in;
@@ -3020,6 +3360,8 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amo
utd.m_payment_id = payment_id;
utd.m_state = wallet2::unconfirmed_transfer_details::pending;
utd.m_timestamp = time(NULL);
+ utd.m_subaddr_account = subaddr_account;
+ utd.m_subaddr_indices = subaddr_indices;
}
//----------------------------------------------------------------------------------------------------
@@ -3090,7 +3432,7 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const
{
std::vector<tx_extra_field> tx_extra_fields;
if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields))
- return cryptonote::null_hash;
+ return crypto::null_hash;
tx_extra_nonce extra_nonce;
crypto::hash payment_id = null_hash;
if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
@@ -3105,7 +3447,7 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const
}
else if (!get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
{
- payment_id = cryptonote::null_hash;
+ payment_id = crypto::null_hash;
}
}
return payment_id;
@@ -3133,28 +3475,45 @@ crypto::hash8 wallet2::get_short_payment_id(const pending_tx &ptx) const
void wallet2::commit_tx(pending_tx& ptx)
{
using namespace cryptonote;
- crypto::hash txid;
-
- COMMAND_RPC_SEND_RAW_TX::request req;
- req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
- req.do_not_relay = false;
- COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp;
- m_daemon_rpc_mutex.lock();
- bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction");
- THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction");
- THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status, daemon_send_resp.reason);
-
- // sanity checks
- for (size_t idx: ptx.selected_transfers)
+
+ if(m_light_wallet)
{
- THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error,
- "Bad output index in selected transfers: " + boost::lexical_cast<std::string>(idx));
+ cryptonote::COMMAND_RPC_SUBMIT_RAW_TX::request oreq;
+ cryptonote::COMMAND_RPC_SUBMIT_RAW_TX::response ores;
+ oreq.address = get_account().get_public_address_str(m_testnet);
+ oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ oreq.tx = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/submit_raw_tx", oreq, ores, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx");
+ // MyMonero and OpenMonero use different status strings
+ THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, ores.status, ores.error);
}
+ else
+ {
+ // Normal submit
+ COMMAND_RPC_SEND_RAW_TX::request req;
+ req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
+ req.do_not_relay = false;
+ COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp;
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout);
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction");
+ THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction");
+ THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status, daemon_send_resp.reason);
+ // sanity checks
+ for (size_t idx: ptx.selected_transfers)
+ {
+ THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error,
+ "Bad output index in selected transfers: " + boost::lexical_cast<std::string>(idx));
+ }
+ }
+ crypto::hash txid;
txid = get_transaction_hash(ptx.tx);
- crypto::hash payment_id = cryptonote::null_hash;
+ crypto::hash payment_id = crypto::null_hash;
std::vector<cryptonote::tx_destination_entry> dests;
uint64_t amount_in = 0;
if (store_tx_info())
@@ -3164,10 +3523,11 @@ void wallet2::commit_tx(pending_tx& ptx)
for(size_t idx: ptx.selected_transfers)
amount_in += m_transfers[idx].amount();
}
- add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount);
+ add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount, ptx.construction_data.subaddr_account, ptx.construction_data.subaddr_indices);
if (store_tx_info())
{
m_tx_keys.insert(std::make_pair(txid, ptx.tx_key));
+ m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys));
}
LOG_PRINT_L2("transaction " << txid << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]");
@@ -3180,8 +3540,8 @@ void wallet2::commit_tx(pending_tx& ptx)
//fee includes dust if dust policy specified it.
LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL
<< "Commission: " << print_money(ptx.fee) << " (dust sent to dust addr: " << print_money((ptx.dust_added_to_fee ? 0 : ptx.dust)) << ")" << ENDL
- << "Balance: " << print_money(balance()) << ENDL
- << "Unlocked: " << print_money(unlocked_balance()) << ENDL
+ << "Balance: " << print_money(balance(ptx.construction_data.subaddr_account)) << ENDL
+ << "Unlocked: " << print_money(unlocked_balance(ptx.construction_data.subaddr_account)) << ENDL
<< "Please, wait for confirmation for your balance to be unlocked.");
}
@@ -3235,7 +3595,8 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri
return false;
}
LOG_PRINT_L2("Saving unsigned tx data: " << oss.str());
- return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + oss.str());
+ std::string ciphertext = encrypt_with_view_secret_key(oss.str());
+ return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + ciphertext);
}
//----------------------------------------------------------------------------------------------------
bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs)
@@ -3253,22 +3614,55 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
LOG_PRINT_L0("Failed to load from " << unsigned_filename);
return false;
}
- const size_t magiclen = strlen(UNSIGNED_TX_PREFIX);
+ const size_t magiclen = strlen(UNSIGNED_TX_PREFIX) - 1;
if (strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen))
{
LOG_PRINT_L0("Bad magic from " << unsigned_filename);
return false;
}
s = s.substr(magiclen);
- try
+ const char version = s[0];
+ s = s.substr(1);
+ if (version == '\003')
+ {
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> exported_txs;
+ }
+ catch (...)
+ {
+ LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
+ return false;
+ }
+ }
+ else if (version == '\004')
{
- std::istringstream iss(s);
- boost::archive::portable_binary_iarchive ar(iss);
- ar >> exported_txs;
+ try
+ {
+ s = decrypt_with_view_secret_key(s);
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> exported_txs;
+ }
+ catch (...)
+ {
+ LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
+ return false;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ LOG_PRINT_L0("Failed to decrypt " << unsigned_filename << ": " << e.what());
+ return false;
+ }
}
- catch (...)
+ else
{
- LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
+ LOG_PRINT_L0("Unsupported version in " << unsigned_filename);
return false;
}
LOG_PRINT_L1("Loaded tx unsigned data from binary: " << exported_txs.txes.size() << " transactions");
@@ -3276,7 +3670,7 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
return true;
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, std::function<bool(const unsigned_tx_set&)> accept_func)
+bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, std::function<bool(const unsigned_tx_set&)> accept_func, bool export_raw)
{
unsigned_tx_set exported_txs;
if(!load_unsigned_tx(unsigned_filename, exported_txs))
@@ -3287,11 +3681,11 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
LOG_PRINT_L1("Transactions rejected by callback");
return false;
}
- return sign_tx(exported_txs, signed_filename, txs);
+ return sign_tx(exported_txs, signed_filename, txs, export_raw);
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs)
+bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, bool export_raw)
{
import_outputs(exported_txs.transfers);
@@ -3299,12 +3693,13 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
signed_tx_set signed_txes;
for (size_t n = 0; n < exported_txs.txes.size(); ++n)
{
- const tools::wallet2::tx_construction_data &sd = exported_txs.txes[n];
+ tools::wallet2::tx_construction_data &sd = exported_txs.txes[n];
LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << sd.sources[0].outputs.size());
signed_txes.ptx.push_back(pending_tx());
tools::wallet2::pending_tx &ptx = signed_txes.ptx.back();
crypto::secret_key tx_key;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sd.sources, sd.splitted_dsts, sd.extra, ptx.tx, sd.unlock_time, tx_key, sd.use_rct);
+ std::vector<crypto::secret_key> additional_tx_keys;
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet);
// we don't test tx size, because we don't know the current limit, due to not having a blockchain,
// and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway,
@@ -3318,6 +3713,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
{
const crypto::hash txid = get_transaction_hash(ptx.tx);
m_tx_keys.insert(std::make_pair(txid, tx_key));
+ m_additional_tx_keys.insert(std::make_pair(txid, additional_tx_keys));
}
std::string key_images;
@@ -3364,8 +3760,28 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
{
return false;
}
- LOG_PRINT_L3("Saving signed tx data: " << oss.str());
- return epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + oss.str());
+ LOG_PRINT_L3("Saving signed tx data (with encryption): " << oss.str());
+ std::string ciphertext = encrypt_with_view_secret_key(oss.str());
+ if (!epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + ciphertext))
+ {
+ LOG_PRINT_L0("Failed to save file to " << signed_filename);
+ return false;
+ }
+ // export signed raw tx without encryption
+ if (export_raw)
+ {
+ for (size_t i = 0; i < signed_txes.ptx.size(); ++i)
+ {
+ 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))
+ {
+ LOG_PRINT_L0("Failed to save file to " << raw_filename);
+ return false;
+ }
+ }
+ }
+ return true;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func)
@@ -3385,22 +3801,55 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
LOG_PRINT_L0("Failed to load from " << signed_filename);
return false;
}
- const size_t magiclen = strlen(SIGNED_TX_PREFIX);
+ const size_t magiclen = strlen(SIGNED_TX_PREFIX) - 1;
if (strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen))
{
LOG_PRINT_L0("Bad magic from " << signed_filename);
return false;
}
s = s.substr(magiclen);
- try
+ const char version = s[0];
+ s = s.substr(1);
+ if (version == '\003')
+ {
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> signed_txs;
+ }
+ catch (...)
+ {
+ LOG_PRINT_L0("Failed to parse data from " << signed_filename);
+ return false;
+ }
+ }
+ else if (version == '\004')
{
- std::istringstream iss(s);
- boost::archive::portable_binary_iarchive ar(iss);
- ar >> signed_txs;
+ try
+ {
+ s = decrypt_with_view_secret_key(s);
+ try
+ {
+ std::istringstream iss(s);
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> signed_txs;
+ }
+ catch (...)
+ {
+ LOG_PRINT_L0("Failed to parse decrypted data from " << signed_filename);
+ return false;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ LOG_PRINT_L0("Failed to decrypt " << signed_filename << ": " << e.what());
+ return false;
+ }
}
- catch (...)
+ else
{
- LOG_PRINT_L0("Failed to parse data from " << signed_filename);
+ LOG_PRINT_L0("Unsupported version in " << signed_filename);
return false;
}
LOG_PRINT_L0("Loaded signed tx data from binary: " << signed_txs.ptx.size() << " transactions");
@@ -3483,6 +3932,8 @@ uint64_t wallet2::get_dynamic_per_kb_fee_estimate()
//----------------------------------------------------------------------------------------------------
uint64_t wallet2::get_per_kb_fee()
{
+ if(m_light_wallet)
+ return m_light_wallet_per_kb_fee;
bool use_dyn_fee = use_fork_rules(HF_VERSION_DYNAMIC_FEE, -720 * 1);
if (!use_dyn_fee)
return FEE_PER_KB;
@@ -3504,7 +3955,7 @@ int wallet2::get_fee_algorithm()
//
// this function will make multiple calls to wallet2::transfer if multiple
// transactions will be required
-std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon)
+std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon)
{
const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, true, true, trusted_daemon);
@@ -3608,10 +4059,134 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
}
}
+
+bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const
+{
+ if (!unlocked) // don't add locked outs
+ return false;
+ if (global_index == real_index) // don't re-add real one
+ return false;
+ auto item = std::make_tuple(global_index, tx_public_key, mask);
+ if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates
+ return false;
+ outs.back().push_back(item);
+ return true;
+}
+
+void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) {
+
+ MDEBUG("LIGHTWALLET - Getting random outs");
+
+ cryptonote::COMMAND_RPC_GET_RANDOM_OUTS::request oreq;
+ cryptonote::COMMAND_RPC_GET_RANDOM_OUTS::response ores;
+
+ size_t light_wallet_requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1);
+
+ // Amounts to ask for
+ // MyMonero api handle amounts and fees as strings
+ for(size_t idx: selected_transfers) {
+ const uint64_t ask_amount = m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount();
+ std::ostringstream amount_ss;
+ amount_ss << ask_amount;
+ oreq.amounts.push_back(amount_ss.str());
+ }
+
+ oreq.count = light_wallet_requested_outputs_count;
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/get_random_outs", oreq, ores, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs");
+ THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs recieved from light wallet node. Error: " + ores.Error);
+
+ // Check if we got enough outputs for each amount
+ for(auto& out: ores.amount_outs) {
+ const uint64_t out_amount = boost::lexical_cast<uint64_t>(out.amount);
+ THROW_WALLET_EXCEPTION_IF(out.outputs.size() < light_wallet_requested_outputs_count , error::wallet_internal_error, "Not enough outputs for amount: " + boost::lexical_cast<std::string>(out.amount));
+ MDEBUG(out.outputs.size() << " outputs for amount "+ boost::lexical_cast<std::string>(out.amount) + " received from light wallet node");
+ }
+
+ MDEBUG("selected transfers size: " << selected_transfers.size());
+
+ for(size_t idx: selected_transfers)
+ {
+ // Create new index
+ outs.push_back(std::vector<get_outs_entry>());
+ outs.back().reserve(fake_outputs_count + 1);
+
+ // add real output first
+ const transfer_details &td = m_transfers[idx];
+ const uint64_t amount = td.is_rct() ? 0 : td.amount();
+ outs.back().push_back(std::make_tuple(td.m_global_output_index, td.get_public_key(), rct::commit(td.amount(), td.m_mask)));
+ MDEBUG("added real output " << string_tools::pod_to_hex(td.get_public_key()));
+
+ // Even if the lightwallet server returns random outputs, we pick them randomly.
+ std::vector<size_t> order;
+ order.resize(light_wallet_requested_outputs_count);
+ for (size_t n = 0; n < order.size(); ++n)
+ order[n] = n;
+ std::shuffle(order.begin(), order.end(), std::default_random_engine(crypto::rand<unsigned>()));
+
+
+ LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs with amounts " << print_money(td.is_rct() ? 0 : td.amount()));
+ MDEBUG("OUTS SIZE: " << outs.back().size());
+ for (size_t o = 0; o < light_wallet_requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o)
+ {
+ // Random pick
+ size_t i = order[o];
+
+ // Find which random output key to use
+ bool found_amount = false;
+ size_t amount_key;
+ for(amount_key = 0; amount_key < ores.amount_outs.size(); ++amount_key)
+ {
+ if(boost::lexical_cast<uint64_t>(ores.amount_outs[amount_key].amount) == amount) {
+ found_amount = true;
+ break;
+ }
+ }
+ THROW_WALLET_EXCEPTION_IF(!found_amount , error::wallet_internal_error, "Outputs for amount " + boost::lexical_cast<std::string>(ores.amount_outs[amount_key].amount) + " not found" );
+
+ LOG_PRINT_L2("Index " << i << "/" << light_wallet_requested_outputs_count << ": idx " << ores.amount_outs[amount_key].outputs[i].global_index << " (real " << td.m_global_output_index << "), unlocked " << "(always in light)" << ", key " << ores.amount_outs[0].outputs[i].public_key);
+
+ // Convert light wallet string data to proper data structures
+ crypto::public_key tx_public_key;
+ rct::key mask = AUTO_VAL_INIT(mask); // decrypted mask - not used here
+ rct::key rct_commit = AUTO_VAL_INIT(rct_commit);
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, ores.amount_outs[amount_key].outputs[i].public_key), error::wallet_internal_error, "Invalid public_key");
+ string_tools::hex_to_pod(ores.amount_outs[amount_key].outputs[i].public_key, tx_public_key);
+ const uint64_t global_index = ores.amount_outs[amount_key].outputs[i].global_index;
+ if(!light_wallet_parse_rct_str(ores.amount_outs[amount_key].outputs[i].rct, tx_public_key, 0, mask, rct_commit, false))
+ rct_commit = rct::zeroCommit(td.amount());
+
+ if (tx_add_fake_output(outs, global_index, tx_public_key, rct_commit, td.m_global_output_index, true)) {
+ MDEBUG("added fake output " << ores.amount_outs[amount_key].outputs[i].public_key);
+ MDEBUG("index " << global_index);
+ }
+ }
+
+ THROW_WALLET_EXCEPTION_IF(outs.back().size() < fake_outputs_count + 1 , error::wallet_internal_error, "Not enough fake outputs found" );
+
+ // Real output is the first. Shuffle outputs
+ MTRACE(outs.back().size() << " outputs added. Sorting outputs by index:");
+ std::sort(outs.back().begin(), outs.back().end(), [](const get_outs_entry &a, const get_outs_entry &b) { return std::get<0>(a) < std::get<0>(b); });
+
+ // Print output order
+ for(auto added_out: outs.back())
+ MTRACE(std::get<0>(added_out));
+
+ }
+}
+
void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count)
{
LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count);
outs.clear();
+
+ if(m_light_wallet && fake_outputs_count > 0) {
+ light_wallet_get_outs(outs, selected_transfers, fake_outputs_count);
+ return;
+ }
+
if (fake_outputs_count > 0)
{
// get histogram for the amounts we need
@@ -3808,14 +4383,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
{
size_t i = base + order[o];
LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key);
- if (req.outputs[i].index == td.m_global_output_index) // don't re-add real one
- continue;
- if (!daemon_resp.outs[i].unlocked) // don't add locked outs
- continue;
- auto item = std::make_tuple(req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask);
- if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates
- continue;
- outs.back().push_back(item);
+ tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked);
}
if (outs.back().size() < fake_outputs_count + 1)
{
@@ -3837,14 +4405,14 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
const transfer_details &td = m_transfers[idx];
std::vector<get_outs_entry> v;
const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount());
- v.push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask));
+ v.push_back(std::make_tuple(td.m_global_output_index, td.get_public_key(), mask));
outs.push_back(v);
}
}
}
template<typename T>
-void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count,
+void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx)
{
@@ -3875,6 +4443,10 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee));
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
+ uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major;
+ for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i)
+ THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts");
+
if (outs.empty())
get_outs(outs, selected_transfers, fake_outputs_count); // may throw
@@ -3917,6 +4489,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
real_oe.second.mask = rct::commit(td.amount(), td.m_mask);
*it_to_replace = real_oe;
src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index);
+ src.real_out_additional_tx_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
src.real_output = it_to_replace - src.outputs.begin();
src.real_output_in_tx_index = td.m_internal_output_index;
detail::print_source_entry(src);
@@ -3927,7 +4500,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts);
if (needed_money < found_money)
{
- change_dts.addr = m_account.get_keys().m_account_address;
+ change_dts.addr = get_subaddress({subaddr_account, 0});
change_dts.amount = found_money - needed_money;
}
@@ -3940,13 +4513,14 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
}
for(auto& d: dust_dsts) {
if (!dust_policy.add_to_fee)
- splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust));
+ splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust, d.is_subaddress));
dust += d.amount;
}
crypto::secret_key tx_key;
+ std::vector<crypto::secret_key> additional_tx_keys;
LOG_PRINT_L2("constructing tx");
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet);
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
@@ -3974,6 +4548,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
ptx.change_dts = change_dts;
ptx.selected_transfers = selected_transfers;
ptx.tx_key = tx_key;
+ ptx.additional_tx_keys = additional_tx_keys;
ptx.dests = dsts;
ptx.construction_data.sources = sources;
ptx.construction_data.change_dts = change_dts;
@@ -3983,10 +4558,15 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = false;
ptx.construction_data.dests = dsts;
+ // record which subaddress indices are being used as inputs
+ ptx.construction_data.subaddr_account = subaddr_account;
+ ptx.construction_data.subaddr_indices.clear();
+ for (size_t idx: selected_transfers)
+ ptx.construction_data.subaddr_indices.insert(m_transfers[idx].m_subaddr_index.minor);
LOG_PRINT_L2("transfer_selected done");
}
-void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count,
+void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx)
{
@@ -4020,6 +4600,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee));
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
+ uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major;
+ for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i)
+ THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts");
+
if (outs.empty())
get_outs(outs, selected_transfers, fake_outputs_count); // may throw
@@ -4036,6 +4620,9 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
src.rct = td.is_rct();
//paste mixin transaction
+ THROW_WALLET_EXCEPTION_IF(outs.size() < out_index + 1 , error::wallet_internal_error, "outs.size() < out_index + 1");
+ THROW_WALLET_EXCEPTION_IF(outs[out_index].size() < fake_outputs_count , error::wallet_internal_error, "fake_outputs_count > random outputs found");
+
typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
for (size_t n = 0; n < fake_outputs_count + 1; ++n)
{
@@ -4057,10 +4644,11 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
tx_output_entry real_oe;
real_oe.first = td.m_global_output_index;
- real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key);
+ real_oe.second.dest = rct::pk2rct(td.get_public_key());
real_oe.second.mask = rct::commit(td.amount(), td.m_mask);
*it_to_replace = real_oe;
src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index);
+ src.real_out_additional_tx_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
src.real_output = it_to_replace - src.outputs.begin();
src.real_output_in_tx_index = td.m_internal_output_index;
src.mask = td.m_mask;
@@ -4087,13 +4675,14 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
}
else
{
- change_dts.addr = m_account.get_keys().m_account_address;
+ change_dts.addr = get_subaddress({subaddr_account, 0});
}
splitted_dsts.push_back(change_dts);
crypto::secret_key tx_key;
+ std::vector<crypto::secret_key> additional_tx_keys;
LOG_PRINT_L2("constructing tx");
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key, true);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_testnet);
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
@@ -4117,6 +4706,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
ptx.change_dts = change_dts;
ptx.selected_transfers = selected_transfers;
ptx.tx_key = tx_key;
+ ptx.additional_tx_keys = additional_tx_keys;
ptx.dests = dsts;
ptx.construction_data.sources = sources;
ptx.construction_data.change_dts = change_dts;
@@ -4126,6 +4716,11 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = true;
ptx.construction_data.dests = dsts;
+ // record which subaddress indices are being used as inputs
+ ptx.construction_data.subaddr_account = subaddr_account;
+ ptx.construction_data.subaddr_indices.clear();
+ for (size_t idx: selected_transfers)
+ ptx.construction_data.subaddr_indices.insert(m_transfers[idx].m_subaddr_index.minor);
LOG_PRINT_L2("transfer_selected_rct done");
}
@@ -4156,7 +4751,7 @@ static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs)
size += (2*64*32+32+64*32) * n_outputs;
// MGs
- size += n_inputs * (32 * (mixin+1) + 32);
+ size += n_inputs * (64 * (mixin+1) + 32);
// mixRing - not serialized, can be reconstructed
/* size += 2 * 32 * (mixin+1) * n_inputs; */
@@ -4182,7 +4777,7 @@ static size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outp
return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES;
}
-std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money) const
+std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const
{
std::vector<size_t> picks;
float current_output_relatdness = 1.0f;
@@ -4193,7 +4788,7 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money) co
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td))
+ if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount()));
picks.push_back(i);
@@ -4208,13 +4803,13 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money) co
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && td.is_rct() && is_transfer_unlocked(td))
+ if (!td.m_spent && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount()));
for (size_t j = i + 1; j < m_transfers.size(); ++j)
{
const transfer_details& td2 = m_transfers[j];
- if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2))
+ if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index)
{
// update our picks if those outputs are less related than any we
// already found. If the same, don't update, and oldest suitable outputs
@@ -4296,6 +4891,450 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr
return count;
}
+bool wallet2::light_wallet_login(bool &new_address)
+{
+ MDEBUG("Light wallet login request");
+ m_light_wallet_connected = false;
+ cryptonote::COMMAND_RPC_LOGIN::request request;
+ cryptonote::COMMAND_RPC_LOGIN::response response;
+ request.address = get_account().get_public_address_str(m_testnet);
+ request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ // Always create account if it doesnt exist.
+ request.create_account = true;
+ m_daemon_rpc_mutex.lock();
+ bool connected = epee::net_utils::invoke_http_json("/login", request, response, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ // MyMonero doesn't send any status message. OpenMonero does.
+ m_light_wallet_connected = connected && (response.status.empty() || response.status == "success");
+ new_address = response.new_address;
+ MDEBUG("Status: " << response.status);
+ MDEBUG("Reason: " << response.reason);
+ MDEBUG("New wallet: " << response.new_address);
+ if(m_light_wallet_connected)
+ {
+ // Clear old data on successfull login.
+ // m_transfers.clear();
+ // m_payments.clear();
+ // m_unconfirmed_payments.clear();
+ }
+ return m_light_wallet_connected;
+}
+
+bool wallet2::light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response)
+{
+ MDEBUG("Light wallet import wallet request");
+ cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::request oreq;
+ oreq.address = get_account().get_public_address_str(m_testnet);
+ oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/import_wallet_request", oreq, response, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "import_wallet_request");
+
+
+ return true;
+}
+
+void wallet2::light_wallet_get_unspent_outs()
+{
+ MDEBUG("Getting unspent outs");
+
+ cryptonote::COMMAND_RPC_GET_UNSPENT_OUTS::request oreq;
+ cryptonote::COMMAND_RPC_GET_UNSPENT_OUTS::response ores;
+
+ oreq.amount = "0";
+ oreq.address = get_account().get_public_address_str(m_testnet);
+ oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ // openMonero specific
+ oreq.dust_threshold = boost::lexical_cast<std::string>(::config::DEFAULT_DUST_THRESHOLD);
+ // below are required by openMonero api - but are not used.
+ oreq.mixin = 0;
+ oreq.use_dust = true;
+
+
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/get_unspent_outs", oreq, ores, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_unspent_outs");
+ THROW_WALLET_EXCEPTION_IF(ores.status == "error", error::wallet_internal_error, ores.reason);
+
+ m_light_wallet_per_kb_fee = ores.per_kb_fee;
+
+ std::unordered_map<crypto::hash,bool> transfers_txs;
+ for(const auto &t: m_transfers)
+ transfers_txs.emplace(t.m_txid,t.m_spent);
+
+ MDEBUG("FOUND " << ores.outputs.size() <<" outputs");
+
+ // return if no outputs found
+ if(ores.outputs.empty())
+ return;
+
+ // Clear old outputs
+ m_transfers.clear();
+
+ for (const auto &o: ores.outputs) {
+ bool spent = false;
+ bool add_transfer = true;
+ crypto::key_image unspent_key_image;
+ crypto::public_key tx_public_key = AUTO_VAL_INIT(tx_public_key);
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field");
+ string_tools::hex_to_pod(o.tx_pub_key, tx_public_key);
+
+ for (const std::string &ski: o.spend_key_images) {
+ spent = false;
+
+ // Check if key image is ours
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, ski), error::wallet_internal_error, "Invalid key image");
+ string_tools::hex_to_pod(ski, unspent_key_image);
+ if(light_wallet_key_image_is_ours(unspent_key_image, tx_public_key, o.index)){
+ MTRACE("Output " << o.public_key << " is spent. Key image: " << ski);
+ spent = true;
+ break;
+ } {
+ MTRACE("Unspent output found. " << o.public_key);
+ }
+ }
+
+ // Check if tx already exists in m_transfers.
+ crypto::hash txid;
+ crypto::public_key tx_pub_key;
+ crypto::public_key public_key;
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_hash), error::wallet_internal_error, "Invalid tx_hash field");
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.public_key), error::wallet_internal_error, "Invalid public_key field");
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field");
+ string_tools::hex_to_pod(o.tx_hash, txid);
+ string_tools::hex_to_pod(o.public_key, public_key);
+ string_tools::hex_to_pod(o.tx_pub_key, tx_pub_key);
+
+ for(auto &t: m_transfers){
+ if(t.get_public_key() == public_key) {
+ t.m_spent = spent;
+ add_transfer = false;
+ break;
+ }
+ }
+
+ if(!add_transfer)
+ continue;
+
+ m_transfers.push_back(boost::value_initialized<transfer_details>());
+ transfer_details& td = m_transfers.back();
+
+ td.m_block_height = o.height;
+ td.m_global_output_index = o.global_index;
+ td.m_txid = txid;
+
+ // Add to extra
+ add_tx_pub_key_to_extra(td.m_tx, tx_pub_key);
+
+ td.m_key_image = unspent_key_image;
+ td.m_key_image_known = !m_watch_only;
+ td.m_amount = o.amount;
+ td.m_pk_index = 0;
+ td.m_internal_output_index = o.index;
+ td.m_spent = spent;
+
+ tx_out txout;
+ txout.target = txout_to_key(public_key);
+ txout.amount = td.m_amount;
+
+ td.m_tx.vout.resize(td.m_internal_output_index + 1);
+ td.m_tx.vout[td.m_internal_output_index] = txout;
+
+ // Add unlock time and coinbase bool got from get_address_txs api call
+ std::unordered_map<crypto::hash,address_tx>::const_iterator found = m_light_wallet_address_txs.find(txid);
+ THROW_WALLET_EXCEPTION_IF(found == m_light_wallet_address_txs.end(), error::wallet_internal_error, "Lightwallet: tx not found in m_light_wallet_address_txs");
+ bool miner_tx = found->second.m_coinbase;
+ td.m_tx.unlock_time = found->second.m_unlock_time;
+
+ if (!o.rct.empty())
+ {
+ // Coinbase tx's
+ if(miner_tx)
+ {
+ td.m_mask = rct::identity();
+ }
+ else
+ {
+ // rct txs
+ // decrypt rct mask, calculate commit hash and compare against blockchain commit hash
+ rct::key rct_commit;
+ light_wallet_parse_rct_str(o.rct, tx_pub_key, td.m_internal_output_index, td.m_mask, rct_commit, true);
+ bool valid_commit = (rct_commit == rct::commit(td.amount(), td.m_mask));
+ if(!valid_commit)
+ {
+ MDEBUG("output index: " << o.global_index);
+ MDEBUG("mask: " + string_tools::pod_to_hex(td.m_mask));
+ MDEBUG("calculated commit: " + string_tools::pod_to_hex(rct::commit(td.amount(), td.m_mask)));
+ MDEBUG("expected commit: " + string_tools::pod_to_hex(rct_commit));
+ MDEBUG("amount: " << td.amount());
+ }
+ THROW_WALLET_EXCEPTION_IF(!valid_commit, error::wallet_internal_error, "Lightwallet: rct commit hash mismatch!");
+ }
+ td.m_rct = true;
+ }
+ else
+ {
+ td.m_mask = rct::identity();
+ td.m_rct = false;
+ }
+ if(!spent)
+ set_unspent(m_transfers.size()-1);
+ m_key_images[td.m_key_image] = m_transfers.size()-1;
+ m_pub_keys[td.get_public_key()] = m_transfers.size()-1;
+ }
+}
+
+bool wallet2::light_wallet_get_address_info(cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response &response)
+{
+ MTRACE(__FUNCTION__);
+
+ cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::request request;
+
+ request.address = get_account().get_public_address_str(m_testnet);
+ request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/get_address_info", request, response, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_address_info");
+ // TODO: Validate result
+ return true;
+}
+
+void wallet2::light_wallet_get_address_txs()
+{
+ MDEBUG("Refreshing light wallet");
+
+ cryptonote::COMMAND_RPC_GET_ADDRESS_TXS::request ireq;
+ cryptonote::COMMAND_RPC_GET_ADDRESS_TXS::response ires;
+
+ ireq.address = get_account().get_public_address_str(m_testnet);
+ ireq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/get_address_txs", ireq, ires, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_address_txs");
+ //OpenMonero sends status=success, Mymonero doesn't.
+ THROW_WALLET_EXCEPTION_IF((!ires.status.empty() && ires.status != "success"), error::no_connection_to_daemon, "get_address_txs");
+
+
+ // Abort if no transactions
+ if(ires.transactions.empty())
+ return;
+
+ // Create searchable vectors
+ std::vector<crypto::hash> payments_txs;
+ for(const auto &p: m_payments)
+ payments_txs.push_back(p.second.m_tx_hash);
+ std::vector<crypto::hash> unconfirmed_payments_txs;
+ for(const auto &up: m_unconfirmed_payments)
+ unconfirmed_payments_txs.push_back(up.second.m_tx_hash);
+
+ // for balance calculation
+ uint64_t wallet_total_sent = 0;
+ uint64_t wallet_total_unlocked_sent = 0;
+ // txs in pool
+ std::vector<crypto::hash> pool_txs;
+
+ for (const auto &t: ires.transactions) {
+ const uint64_t total_received = t.total_received;
+ uint64_t total_sent = t.total_sent;
+
+ // Check key images - subtract fake outputs from total_sent
+ for(const auto &so: t.spent_outputs)
+ {
+ crypto::public_key tx_public_key;
+ crypto::key_image key_image;
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, so.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field");
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, so.key_image), error::wallet_internal_error, "Invalid key_image field");
+ string_tools::hex_to_pod(so.tx_pub_key, tx_public_key);
+ string_tools::hex_to_pod(so.key_image, key_image);
+
+ if(!light_wallet_key_image_is_ours(key_image, tx_public_key, so.out_index)) {
+ THROW_WALLET_EXCEPTION_IF(so.amount > t.total_sent, error::wallet_internal_error, "Lightwallet: total sent is negative!");
+ total_sent -= so.amount;
+ }
+ }
+
+ // Do not add tx if empty.
+ if(total_sent == 0 && total_received == 0)
+ continue;
+
+ crypto::hash payment_id = null_hash;
+ crypto::hash tx_hash;
+
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, t.payment_id), error::wallet_internal_error, "Invalid payment_id field");
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, t.hash), error::wallet_internal_error, "Invalid hash field");
+ string_tools::hex_to_pod(t.payment_id, payment_id);
+ string_tools::hex_to_pod(t.hash, tx_hash);
+
+ // lightwallet specific info
+ bool incoming = (total_received > total_sent);
+ address_tx address_tx;
+ address_tx.m_tx_hash = tx_hash;
+ address_tx.m_incoming = incoming;
+ address_tx.m_amount = incoming ? total_received - total_sent : total_sent - total_received;
+ address_tx.m_block_height = t.height;
+ address_tx.m_unlock_time = t.unlock_time;
+ address_tx.m_timestamp = t.timestamp;
+ address_tx.m_coinbase = t.coinbase;
+ address_tx.m_mempool = t.mempool;
+ m_light_wallet_address_txs.emplace(tx_hash,address_tx);
+
+ // populate data needed for history (m_payments, m_unconfirmed_payments, m_confirmed_txs)
+ // INCOMING transfers
+ if(total_received > total_sent) {
+ payment_details payment;
+ payment.m_tx_hash = tx_hash;
+ payment.m_amount = total_received - total_sent;
+ payment.m_block_height = t.height;
+ payment.m_unlock_time = t.unlock_time;
+ payment.m_timestamp = t.timestamp;
+
+ if (t.mempool) {
+ if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) {
+ pool_txs.push_back(tx_hash);
+ m_unconfirmed_payments.emplace(tx_hash, payment);
+ if (0 != m_callback) {
+ m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount);
+ }
+ }
+ } else {
+ if (std::find(payments_txs.begin(), payments_txs.end(), tx_hash) == payments_txs.end()) {
+ m_payments.emplace(tx_hash, payment);
+ if (0 != m_callback) {
+ m_callback->on_lw_money_received(t.height, payment.m_tx_hash, payment.m_amount);
+ }
+ }
+ }
+ // Outgoing transfers
+ } else {
+ uint64_t amount_sent = total_sent - total_received;
+ cryptonote::transaction dummy_tx; // not used by light wallet
+ // increase wallet total sent
+ wallet_total_sent += total_sent;
+ if (t.mempool)
+ {
+ // Handled by add_unconfirmed_tx in commit_tx
+ // If sent from another wallet instance we need to add it
+ if(m_unconfirmed_txs.find(tx_hash) == m_unconfirmed_txs.end())
+ {
+ unconfirmed_transfer_details utd;
+ utd.m_amount_in = amount_sent;
+ utd.m_amount_out = amount_sent;
+ utd.m_change = 0;
+ utd.m_payment_id = payment_id;
+ utd.m_timestamp = t.timestamp;
+ utd.m_state = wallet2::unconfirmed_transfer_details::pending;
+ m_unconfirmed_txs.emplace(tx_hash,utd);
+ }
+ }
+ else
+ {
+ // Only add if new
+ auto confirmed_tx = m_confirmed_txs.find(tx_hash);
+ if(confirmed_tx == m_confirmed_txs.end()) {
+ // tx is added to m_unconfirmed_txs - move to confirmed
+ if(m_unconfirmed_txs.find(tx_hash) != m_unconfirmed_txs.end())
+ {
+ process_unconfirmed(tx_hash, dummy_tx, t.height);
+ }
+ // Tx sent by another wallet instance
+ else
+ {
+ confirmed_transfer_details ctd;
+ ctd.m_amount_in = amount_sent;
+ ctd.m_amount_out = amount_sent;
+ ctd.m_change = 0;
+ ctd.m_payment_id = payment_id;
+ ctd.m_block_height = t.height;
+ ctd.m_timestamp = t.timestamp;
+ m_confirmed_txs.emplace(tx_hash,ctd);
+ }
+ if (0 != m_callback)
+ {
+ m_callback->on_lw_money_spent(t.height, tx_hash, amount_sent);
+ }
+ }
+ // If not new - check the amount and update if necessary.
+ // when sending a tx to same wallet the receiving amount has to be credited
+ else
+ {
+ if(confirmed_tx->second.m_amount_in != amount_sent || confirmed_tx->second.m_amount_out != amount_sent)
+ {
+ MDEBUG("Adjusting amount sent/received for tx: <" + t.hash + ">. Is tx sent to own wallet? " << print_money(amount_sent) << " != " << print_money(confirmed_tx->second.m_amount_in));
+ confirmed_tx->second.m_amount_in = amount_sent;
+ confirmed_tx->second.m_amount_out = amount_sent;
+ confirmed_tx->second.m_change = 0;
+ }
+ }
+ }
+ }
+ }
+ // TODO: purge old unconfirmed_txs
+ remove_obsolete_pool_txs(pool_txs);
+
+ // Calculate wallet balance
+ m_light_wallet_balance = ires.total_received-wallet_total_sent;
+ // MyMonero doesnt send unlocked balance
+ if(ires.total_received_unlocked > 0)
+ m_light_wallet_unlocked_balance = ires.total_received_unlocked-wallet_total_sent;
+ else
+ m_light_wallet_unlocked_balance = m_light_wallet_balance;
+}
+
+bool wallet2::light_wallet_parse_rct_str(const std::string& rct_string, const crypto::public_key& tx_pub_key, uint64_t internal_output_index, rct::key& decrypted_mask, rct::key& rct_commit, bool decrypt) const
+{
+ // rct string is empty if output is non RCT
+ if (rct_string.empty())
+ return false;
+ // rct_string is a string with length 64+64+64 (<rct commit> + <encrypted mask> + <rct amount>)
+ rct::key encrypted_mask;
+ std::string rct_commit_str = rct_string.substr(0,64);
+ std::string encrypted_mask_str = rct_string.substr(64,64);
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, rct_commit_str), error::wallet_internal_error, "Invalid rct commit hash: " + rct_commit_str);
+ THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, encrypted_mask_str), error::wallet_internal_error, "Invalid rct mask: " + encrypted_mask_str);
+ string_tools::hex_to_pod(rct_commit_str, rct_commit);
+ string_tools::hex_to_pod(encrypted_mask_str, encrypted_mask);
+ if (decrypt) {
+ // Decrypt the mask
+ crypto::key_derivation derivation;
+ generate_key_derivation(tx_pub_key, get_account().get_keys().m_view_secret_key, derivation);
+ crypto::secret_key scalar;
+ crypto::derivation_to_scalar(derivation, internal_output_index, scalar);
+ sc_sub(decrypted_mask.bytes,encrypted_mask.bytes,rct::hash_to_scalar(rct::sk2rct(scalar)).bytes);
+ }
+ return true;
+}
+
+bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, const crypto::public_key& tx_public_key, uint64_t out_index)
+{
+ // Lookup key image from cache
+ std::map<uint64_t, crypto::key_image> index_keyimage_map;
+ std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> >::const_iterator found_pub_key = m_key_image_cache.find(tx_public_key);
+ if(found_pub_key != m_key_image_cache.end()) {
+ // pub key found. key image for index cached?
+ index_keyimage_map = found_pub_key->second;
+ std::map<uint64_t,crypto::key_image>::const_iterator index_found = index_keyimage_map.find(out_index);
+ if(index_found != index_keyimage_map.end())
+ return key_image == index_found->second;
+ }
+
+ // Not in cache - calculate key image
+ crypto::key_image calculated_key_image;
+ cryptonote::keypair in_ephemeral;
+
+ // Subaddresses aren't supported in mymonero/openmonero yet. Using empty values.
+ const std::vector<crypto::public_key> additional_tx_pub_keys;
+ const crypto::public_key pkey = crypto::null_pkey;
+
+ cryptonote::generate_key_image_helper(get_account().get_keys(), m_subaddresses, pkey, tx_public_key, additional_tx_pub_keys, out_index, in_ephemeral, calculated_key_image);
+ index_keyimage_map.emplace(out_index, calculated_key_image);
+ m_key_image_cache.emplace(tx_public_key, index_keyimage_map);
+ return key_image == calculated_key_image;
+}
+
// Another implementation of transaction creation that is hopefully better
// While there is anything left to pay, it goes through random outputs and tries
// to fill the next destination/amount. If it fully fills it, it will use the
@@ -4311,10 +5350,14 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr
// This system allows for sending (almost) the entire balance, since it does
// not generate spurious change in all txes, thus decreasing the instantaneous
// usable balance.
-std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon)
{
- std::vector<size_t> unused_transfers_indices;
- std::vector<size_t> unused_dust_indices;
+ if(m_light_wallet) {
+ // Populate m_transfers
+ light_wallet_get_unspent_outs();
+ }
+ std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_transfers_indices_per_subaddr;
+ std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_dust_indices_per_subaddr;
uint64_t needed_money;
uint64_t accumulated_fee, accumulated_outputs, accumulated_change;
struct TX {
@@ -4324,23 +5367,24 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
pending_tx ptx;
size_t bytes;
- void add(const account_public_address &addr, uint64_t amount, unsigned int original_output_index, bool merge_destinations) {
+ void add(const account_public_address &addr, bool is_subaddress, uint64_t amount, unsigned int original_output_index, bool merge_destinations) {
if (merge_destinations)
{
std::vector<cryptonote::tx_destination_entry>::iterator i;
i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &addr, sizeof(addr)); });
if (i == dsts.end())
{
- dsts.push_back(tx_destination_entry(0,addr));
+ dsts.push_back(tx_destination_entry(0,addr,is_subaddress));
i = dsts.end() - 1;
}
i->amount += amount;
}
else
{
- THROW_WALLET_EXCEPTION_IF(original_output_index > dsts.size(), error::wallet_internal_error, "original_output_index too large");
+ THROW_WALLET_EXCEPTION_IF(original_output_index > dsts.size(), error::wallet_internal_error,
+ std::string("original_output_index too large: ") + std::to_string(original_output_index) + " > " + std::to_string(dsts.size()));
if (original_output_index == dsts.size())
- dsts.push_back(tx_destination_entry(0,addr));
+ dsts.push_back(tx_destination_entry(0,addr,is_subaddress));
THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &addr, sizeof(addr)), error::wallet_internal_error, "Mismatched destination address");
dsts[original_output_index].amount += amount;
}
@@ -4372,29 +5416,90 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// throw if attempting a transaction with no money
THROW_WALLET_EXCEPTION_IF(needed_money == 0, error::zero_destination);
- // gather all our dust and non dust outputs
+ std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account);
+
+ if (subaddr_indices.empty()) // "index=<N1>[,<N2>,...]" wasn't specified -> use all the indices with non-zero unlocked bakance
+ {
+ for (const auto& i : balance_per_subaddr)
+ subaddr_indices.insert(i.first);
+ }
+
+ // early out if we know we can't make it anyway
+ // we could also check for being within FEE_PER_KB, but if the fee calculation
+ // ever changes, this might be missed, so let this go through
+ uint64_t balance_subtotal = 0;
+ for (uint32_t index_minor : subaddr_indices)
+ balance_subtotal += balance_per_subaddr[index_minor];
+ THROW_WALLET_EXCEPTION_IF(needed_money > balance_subtotal, error::not_enough_money,
+ balance_subtotal, needed_money, 0);
+
+ for (uint32_t i : subaddr_indices)
+ LOG_PRINT_L2("Candidate subaddress index for spending: " << i);
+
+ // gather all dust and non-dust outputs belonging to specified subaddresses
+ size_t num_nondust_outputs = 0;
+ size_t num_dust_outputs = 0;
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td))
+ if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
+ const uint32_t index_minor = td.m_subaddr_index.minor;
+ auto find_predicate = [&index_minor](const std::pair<uint32_t, std::vector<size_t>>& x) { return x.first == index_minor; };
if ((td.is_rct()) || is_valid_decomposed_amount(td.amount()))
- unused_transfers_indices.push_back(i);
+ {
+ auto found = std::find_if(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), find_predicate);
+ if (found == unused_transfers_indices_per_subaddr.end())
+ {
+ unused_transfers_indices_per_subaddr.push_back({index_minor, {i}});
+ }
+ else
+ {
+ found->second.push_back(i);
+ }
+ ++num_nondust_outputs;
+ }
else
- unused_dust_indices.push_back(i);
+ {
+ auto found = std::find_if(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), find_predicate);
+ if (found == unused_dust_indices_per_subaddr.end())
+ {
+ unused_dust_indices_per_subaddr.push_back({index_minor, {i}});
+ }
+ else
+ {
+ found->second.push_back(i);
+ }
+ ++num_dust_outputs;
+ }
}
}
- LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs");
- // early out if we know we can't make it anyway
- // we could also check for being within FEE_PER_KB, but if the fee calculation
- // ever changes, this might be missed, so let this go through
- THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance(), error::not_enough_money,
- unlocked_balance(), needed_money, 0);
+ // shuffle & sort output indices
+ {
+ std::random_device rd;
+ std::mt19937 g(rd());
+ std::shuffle(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), g);
+ std::shuffle(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), g);
+ auto sort_predicate = [&balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y)
+ {
+ return balance_per_subaddr[x.first] > balance_per_subaddr[y.first];
+ };
+ std::sort(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), sort_predicate);
+ std::sort(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), sort_predicate);
+ }
- if (unused_dust_indices.empty() && unused_transfers_indices.empty())
+ LOG_PRINT_L2("Starting with " << num_nondust_outputs << " non-dust outputs and " << num_dust_outputs << " dust outputs");
+
+ if (unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty())
return std::vector<wallet2::pending_tx>();
+ // if empty, put dummy entry so that the front can be referenced later in the loop
+ if (unused_dust_indices_per_subaddr.empty())
+ unused_dust_indices_per_subaddr.push_back({});
+ if (unused_transfers_indices_per_subaddr.empty())
+ unused_transfers_indices_per_subaddr.push_back({});
+
// start with an empty tx
txes.push_back(TX());
accumulated_fee = 0;
@@ -4413,17 +5518,36 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
std::vector<size_t> preferred_inputs;
uint64_t rct_outs_needed = 2 * (fake_outs_count + 1);
rct_outs_needed += 100; // some fudge factor since we don't know how many are locked
- if (use_rct && get_num_rct_outputs() >= rct_outs_needed)
+ if (use_rct)
{
// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
// will get us a known fee.
uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier);
- preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee);
+ preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices);
if (!preferred_inputs.empty())
{
string s;
for (auto i: preferred_inputs) s += boost::lexical_cast<std::string>(i) + "(" + print_money(m_transfers[i].amount()) + ") ";
- LOG_PRINT_L1("Found preferred rct inputs for rct tx: " << s);
+ LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s);
+
+ // bring the list of available outputs stored by the same subaddress index to the front of the list
+ uint32_t index_minor = m_transfers[preferred_inputs[0]].m_subaddr_index.minor;
+ for (size_t i = 1; i < unused_transfers_indices_per_subaddr.size(); ++i)
+ {
+ if (unused_transfers_indices_per_subaddr[i].first == index_minor)
+ {
+ std::swap(unused_transfers_indices_per_subaddr[0], unused_transfers_indices_per_subaddr[i]);
+ break;
+ }
+ }
+ for (size_t i = 1; i < unused_dust_indices_per_subaddr.size(); ++i)
+ {
+ if (unused_dust_indices_per_subaddr[i].first == index_minor)
+ {
+ std::swap(unused_dust_indices_per_subaddr[0], unused_dust_indices_per_subaddr[i]);
+ break;
+ }
+ }
}
}
LOG_PRINT_L2("done checking preferred");
@@ -4433,23 +5557,22 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// - or we need to gather more fee
// - or we have just one input in that tx, which is rct (to try and make all/most rct txes 2/2)
unsigned int original_output_index = 0;
- while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), unused_transfers_indices, unused_dust_indices)) {
+ std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second;
+ std::vector<size_t>* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second;
+ while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) {
TX &tx = txes.back();
- LOG_PRINT_L2("Start of loop with " << unused_transfers_indices.size() << " " << unused_dust_indices.size());
- LOG_PRINT_L2("unused_transfers_indices:");
- for (auto t: unused_transfers_indices)
- LOG_PRINT_L2(" " << t);
- LOG_PRINT_L2("unused_dust_indices:");
- for (auto t: unused_dust_indices)
- LOG_PRINT_L2(" " << t);
+ LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size());
+ LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size());
+ LOG_PRINT_L2("unused_transfers_indices: " << strjoin(*unused_transfers_indices, " "));
+ LOG_PRINT_L2("unused_dust_indices:" << strjoin(*unused_dust_indices, " "));
LOG_PRINT_L2("dsts size " << dsts.size() << ", first " << (dsts.empty() ? -1 : dsts[0].amount));
LOG_PRINT_L2("adding_fee " << adding_fee << ", use_rct " << use_rct);
// if we need to spend money and don't have any left, we fail
- if (unused_dust_indices.empty() && unused_transfers_indices.empty()) {
+ if (unused_dust_indices->empty() && unused_transfers_indices->empty()) {
LOG_PRINT_L2("No more outputs to choose from");
- THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee);
+ THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(subaddr_account), needed_money, accumulated_fee + needed_fee);
}
// get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc)
@@ -4457,12 +5580,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
size_t idx;
if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) {
// the "make rct txes 2/2" case - we pick a small value output to "clean up" the wallet too
- std::vector<size_t> indices = get_only_rct(unused_dust_indices, unused_transfers_indices);
+ std::vector<size_t> indices = get_only_rct(*unused_dust_indices, *unused_transfers_indices);
idx = pop_best_value(indices, tx.selected_transfers, true);
// we might not want to add it if it's a large output and we don't have many left
if (m_transfers[idx].amount() >= m_min_output_value) {
- if (get_count_above(m_transfers, unused_transfers_indices, m_min_output_value) < m_min_output_count) {
+ if (get_count_above(m_transfers, *unused_transfers_indices, m_min_output_value) < m_min_output_count) {
LOG_PRINT_L2("Second output was not strictly needed, and we're running out of outputs above " << print_money(m_min_output_value) << ", not adding");
break;
}
@@ -4476,14 +5599,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("Second output was not strictly needed, and relatedness " << relatedness << ", not adding");
break;
}
- pop_if_present(unused_transfers_indices, idx);
- pop_if_present(unused_dust_indices, idx);
+ pop_if_present(*unused_transfers_indices, idx);
+ pop_if_present(*unused_dust_indices, idx);
} else if (!preferred_inputs.empty()) {
idx = pop_back(preferred_inputs);
- pop_if_present(unused_transfers_indices, idx);
- pop_if_present(unused_dust_indices, idx);
+ pop_if_present(*unused_transfers_indices, idx);
+ pop_if_present(*unused_dust_indices, idx);
} else
- idx = pop_best_value(unused_transfers_indices.empty() ? unused_dust_indices : unused_transfers_indices, tx.selected_transfers);
+ idx = pop_best_value(unused_transfers_indices->empty() ? *unused_dust_indices : *unused_transfers_indices, tx.selected_transfers);
const transfer_details &td = m_transfers[idx];
LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount()) << ", ki " << td.m_key_image);
@@ -4506,9 +5629,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit))
{
// we can fully pay that destination
- LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].addr) <<
+ LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(dsts[0].amount));
- tx.add(dsts[0].addr, dsts[0].amount, original_output_index, m_merge_destinations);
+ tx.add(dsts[0].addr, dsts[0].is_subaddress, dsts[0].amount, original_output_index, m_merge_destinations);
available_amount -= dsts[0].amount;
dsts[0].amount = 0;
pop_index(dsts, 0);
@@ -4517,9 +5640,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) {
// we can partially fill that destination
- LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].addr) <<
+ LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));
- tx.add(dsts[0].addr, available_amount, original_output_index, m_merge_destinations);
+ tx.add(dsts[0].addr, dsts[0].is_subaddress, available_amount, original_output_index, m_merge_destinations);
dsts[0].amount -= available_amount;
available_amount = 0;
}
@@ -4571,7 +5694,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
if (i->amount > needed_fee)
{
uint64_t new_paid_amount = i->amount /*+ test_ptx.fee*/ - needed_fee;
- LOG_PRINT_L2("Adjusting amount paid to " << get_account_address_as_str(m_testnet, i->addr) << " from " <<
+ LOG_PRINT_L2("Adjusting amount paid to " << get_account_address_as_str(m_testnet, i->is_subaddress, i->addr) << " from " <<
print_money(i->amount) << " to " << print_money(new_paid_amount) << " to accommodate " <<
print_money(needed_fee) << " fee");
dsts[0].amount += i->amount - new_paid_amount;
@@ -4616,15 +5739,32 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
{
LOG_PRINT_L2("We have more to pay, starting another tx");
txes.push_back(TX());
+ original_output_index = 0;
}
}
}
+
+ // if unused_*_indices is empty while unused_*_indices_per_subaddr has multiple elements, and if we still have something to pay,
+ // pop front of unused_*_indices_per_subaddr and have unused_*_indices point to the front of unused_*_indices_per_subaddr
+ if ((!dsts.empty() && dsts[0].amount > 0) || adding_fee)
+ {
+ if (unused_transfers_indices->empty() && unused_transfers_indices_per_subaddr.size() > 1)
+ {
+ unused_transfers_indices_per_subaddr.erase(unused_transfers_indices_per_subaddr.begin());
+ unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second;
+ }
+ if (unused_dust_indices->empty() && unused_dust_indices_per_subaddr.size() > 1)
+ {
+ unused_dust_indices_per_subaddr.erase(unused_dust_indices_per_subaddr.begin());
+ unused_dust_indices = &unused_dust_indices_per_subaddr[0].second;
+ }
+ }
}
if (adding_fee)
{
LOG_PRINT_L1("We ran out of outputs while trying to gather final fee");
- THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee);
+ THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(subaddr_account), needed_money, accumulated_fee + needed_fee);
}
LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) <<
@@ -4648,21 +5788,37 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
return ptx_vector;
}
-std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon)
{
std::vector<size_t> unused_transfers_indices;
std::vector<size_t> unused_dust_indices;
const bool use_rct = use_fork_rules(4, 0);
- // gather all our dust and non dust outputs
+ THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account) == 0, error::wallet_internal_error, "No unclocked balance in the entire wallet");
+
+ std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account);
+
+ if (subaddr_indices.empty())
+ {
+ // in case subaddress index wasn't specified, choose non-empty subaddress randomly (with index=0 being chosen last)
+ if (balance_per_subaddr.count(0) == 1 && balance_per_subaddr.size() > 1)
+ balance_per_subaddr.erase(0);
+ auto i = balance_per_subaddr.begin();
+ std::advance(i, crypto::rand<size_t>() % balance_per_subaddr.size());
+ subaddr_indices.insert(i->first);
+ }
+ for (uint32_t i : subaddr_indices)
+ LOG_PRINT_L2("Spending from subaddress index " << i);
+
+ // gather all dust and non-dust outputs of specified subaddress
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td))
+ if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
if (below == 0 || td.amount() < below)
{
- if (td.is_rct() || is_valid_decomposed_amount(td.amount()))
+ if ((td.is_rct()) || is_valid_decomposed_amount(td.amount()))
unused_transfers_indices.push_back(i);
else
unused_dust_indices.push_back(i);
@@ -4670,10 +5826,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
}
}
- return create_transactions_from(address, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon);
+ THROW_WALLET_EXCEPTION_IF(unused_transfers_indices.empty() && unused_dust_indices.empty(), error::not_enough_money, 0, 0, 0); // not sure if a new error class (something like 'cant_sweep_empty'?) should be introduced
+
+ return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon);
}
-std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon)
{
uint64_t accumulated_fee, accumulated_outputs, accumulated_change;
struct TX {
@@ -4736,7 +5894,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
needed_fee = 0;
- tx.dsts.push_back(tx_destination_entry(1, address));
+ tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress));
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " <<
tx.selected_transfers.size() << " outputs");
@@ -4758,7 +5916,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
LOG_PRINT_L2("We made a tx, adjusting fee and saving it");
tx.dsts[0].amount = available_for_fee - needed_fee;
if (use_rct)
- transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
+ transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
test_tx, test_ptx);
else
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
@@ -4805,21 +5963,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
// if we made it this far, we're OK to actually send the transactions
return ptx_vector;
}
-
-uint64_t wallet2::unlocked_dust_balance(const tx_dust_policy &dust_policy) const
-{
- uint64_t money = 0;
- std::list<transfer_container::iterator> selected_transfers;
- for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i)
- {
- const transfer_details& td = *i;
- if (!td.m_spent && td.amount() < dust_policy.dust_threshold && is_transfer_unlocked(td))
- {
- money += td.amount();
- }
- }
- return money;
-}
//----------------------------------------------------------------------------------------------------
void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height)
{
@@ -4829,6 +5972,9 @@ void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height)
//----------------------------------------------------------------------------------------------------
bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks)
{
+ // TODO: How to get fork rule info from light wallet node?
+ if(m_light_wallet)
+ return true;
uint64_t height, earliest_height;
boost::optional<std::string> result = m_node_rpc_proxy.get_height(height);
throw_on_rpc_response_error(result, "get_info");
@@ -4994,15 +6140,19 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo
unmixable_transfer_outputs.push_back(n);
}
- return create_transactions_from(m_account_public_address, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>(), trusted_daemon);
+ return create_transactions_from(m_account_public_address, false, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>(), trusted_daemon);
}
-bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const
+bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const
{
+ additional_tx_keys.clear();
const std::unordered_map<crypto::hash, crypto::secret_key>::const_iterator i = m_tx_keys.find(txid);
if (i == m_tx_keys.end())
return false;
tx_key = i->second;
+ const auto j = m_additional_tx_keys.find(txid);
+ if (j != m_additional_tx_keys.end())
+ additional_tx_keys = j->second;
return true;
}
@@ -5157,6 +6307,15 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
// more than one, loop and search
const cryptonote::account_keys& keys = m_account.get_keys();
size_t pk_index = 0;
+
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
+ std::vector<crypto::key_derivation> additional_derivations;
+ for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
+ {
+ additional_derivations.push_back({});
+ generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back());
+ }
+
while (find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index++)) {
const crypto::public_key tx_pub_key = pub_key_field.pub_key;
crypto::key_derivation derivation;
@@ -5164,10 +6323,9 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
for (size_t i = 0; i < td.m_tx.vout.size(); ++i)
{
- uint64_t money_transfered = 0;
- bool error = false, received = false;
- check_acc_out_precomp(keys.m_account_address.m_spend_public_key, td.m_tx.vout[i], derivation, i, received, money_transfered, error);
- if (!error && received)
+ tx_scan_info_t tx_scan_info;
+ check_acc_out_precomp(td.m_tx.vout[i], derivation, additional_derivations, i, tx_scan_info);
+ if (!tx_scan_info.error && tx_scan_info.received)
return tx_pub_key;
}
}
@@ -5175,10 +6333,10 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
// we found no key yielding an output
THROW_WALLET_EXCEPTION_IF(true, error::wallet_internal_error,
"Public key yielding at least one output wasn't found in the transaction extra");
- return cryptonote::null_pkey;
+ return crypto::null_pkey;
}
-bool wallet2::export_key_images(const std::string filename)
+bool wallet2::export_key_images(const std::string &filename)
{
std::vector<std::pair<crypto::key_image, crypto::signature>> ski = export_key_images();
std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC));
@@ -5226,11 +6384,13 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key
}
crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td);
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
// generate ephemeral secret key
crypto::key_image ki;
cryptonote::keypair in_ephemeral;
- cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, ki);
+ bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, ki);
+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && ki != td.m_key_image,
error::wallet_internal_error, "key_image generated not matched with cached key image");
@@ -5381,6 +6541,9 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
}
spent = 0;
unspent = 0;
+ std::unordered_set<crypto::hash> spent_txids; // For each spent key image, search for a tx in m_transfers that uses it as input.
+ std::vector<size_t> swept_transfers; // If such a spending tx wasn't found in m_transfers, this means the spending tx
+ // was created by sweep_all, so we can't know the spent height and other detailed info.
for(size_t i = 0; i < m_transfers.size(); ++i)
{
transfer_details &td = m_transfers[i];
@@ -5391,8 +6554,158 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
unspent += amount;
LOG_PRINT_L2("Transfer " << i << ": " << print_money(amount) << " (" << td.m_global_output_index << "): "
<< (td.m_spent ? "spent" : "unspent") << " (key image " << req.key_images[i] << ")");
+
+ if (i < daemon_resp.spent_status.size() && daemon_resp.spent_status[i] == COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_BLOCKCHAIN)
+ {
+ bool is_spent_tx_found = false;
+ for (auto it = m_transfers.rbegin(); &(*it) != &td; ++it)
+ {
+ bool is_spent_tx = false;
+ for(const cryptonote::txin_v& in : it->m_tx.vin)
+ {
+ if(in.type() == typeid(cryptonote::txin_to_key) && td.m_key_image == boost::get<cryptonote::txin_to_key>(in).k_image)
+ {
+ is_spent_tx = true;
+ break;
+ }
+ }
+ if (is_spent_tx)
+ {
+ is_spent_tx_found = true;
+ spent_txids.insert(it->m_txid);
+ break;
+ }
+ }
+
+ if (!is_spent_tx_found)
+ swept_transfers.push_back(i);
+ }
}
MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent");
+
+ if (check_spent)
+ {
+ // query outgoing txes
+ COMMAND_RPC_GET_TRANSACTIONS::request gettxs_req;
+ COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res;
+ gettxs_req.decode_as_json = false;
+ for (const crypto::hash& spent_txid : spent_txids)
+ gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid));
+ m_daemon_rpc_mutex.lock();
+ bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout);
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
+ THROW_WALLET_EXCEPTION_IF(gettxs_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions");
+ THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error,
+ "daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size()));
+
+ // process each outgoing tx
+ auto spent_txid = spent_txids.begin();
+ for (const COMMAND_RPC_GET_TRANSACTIONS::entry& e : gettxs_res.txs)
+ {
+ THROW_WALLET_EXCEPTION_IF(e.in_pool, error::wallet_internal_error, "spent tx isn't supposed to be in txpool");
+
+ // parse tx
+ cryptonote::blobdata bd;
+ THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(e.as_hex, bd), error::wallet_internal_error, "parse_hexstr_to_binbuff failed");
+ cryptonote::transaction spent_tx;
+ crypto::hash spnet_txid_parsed, spent_txid_prefix;
+ THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, spent_tx, spnet_txid_parsed, spent_txid_prefix), error::wallet_internal_error, "parse_and_validate_tx_from_blob failed");
+ THROW_WALLET_EXCEPTION_IF(*spent_txid != spnet_txid_parsed, error::wallet_internal_error, "parsed txid mismatch");
+
+ // get received (change) amount
+ uint64_t tx_money_got_in_outs = 0;
+ const cryptonote::account_keys& keys = m_account.get_keys();
+ const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(spent_tx);
+ crypto::key_derivation derivation;
+ generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation);
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(spent_tx);
+ std::vector<crypto::key_derivation> additional_derivations;
+ for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
+ {
+ additional_derivations.push_back({});
+ generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back());
+ }
+ size_t output_index = 0;
+ for (const cryptonote::tx_out& out : spent_tx.vout)
+ {
+ tx_scan_info_t tx_scan_info;
+ check_acc_out_precomp(out, derivation, additional_derivations, output_index, tx_scan_info);
+ THROW_WALLET_EXCEPTION_IF(tx_scan_info.error, error::wallet_internal_error, "check_acc_out_precomp failed");
+ if (tx_scan_info.received)
+ {
+ if (tx_scan_info.money_transfered == 0)
+ {
+ rct::key mask;
+ tx_scan_info.money_transfered = tools::decodeRct(spent_tx.rct_signatures, tx_scan_info.received->derivation, output_index, mask);
+ }
+ tx_money_got_in_outs += tx_scan_info.money_transfered;
+ }
+ ++output_index;
+ }
+
+ // get spent amount
+ uint64_t tx_money_spent_in_ins = 0;
+ uint32_t subaddr_account = (uint32_t)-1;
+ std::set<uint32_t> subaddr_indices;
+ for (const cryptonote::txin_v& in : spent_tx.vin)
+ {
+ if (in.type() != typeid(cryptonote::txin_to_key))
+ continue;
+ auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image);
+ if (it != m_key_images.end())
+ {
+ const transfer_details& td = m_transfers[it->second];
+ uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount;
+ if (amount > 0)
+ {
+ THROW_WALLET_EXCEPTION_IF(amount != td.amount(), error::wallet_internal_error,
+ std::string("Inconsistent amount in tx input: got ") + print_money(amount) +
+ std::string(", expected ") + print_money(td.amount()));
+ }
+ amount = td.amount();
+ tx_money_spent_in_ins += amount;
+
+ LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << *spent_txid);
+ set_spent(it->second, e.block_height);
+ if (m_callback)
+ m_callback->on_money_spent(e.block_height, *spent_txid, spent_tx, amount, spent_tx, td.m_subaddr_index);
+ if (subaddr_account != (uint32_t)-1 && subaddr_account != td.m_subaddr_index.major)
+ LOG_PRINT_L0("WARNING: This tx spends outputs received by different subaddress accounts, which isn't supposed to happen");
+ subaddr_account = td.m_subaddr_index.major;
+ subaddr_indices.insert(td.m_subaddr_index.minor);
+ }
+ }
+
+ // create outgoing payment
+ process_outgoing(*spent_txid, spent_tx, e.block_height, e.block_timestamp, tx_money_spent_in_ins, tx_money_got_in_outs, subaddr_account, subaddr_indices);
+
+ // erase corresponding incoming payment
+ for (auto j = m_payments.begin(); j != m_payments.end(); ++j)
+ {
+ if (j->second.m_tx_hash == *spent_txid)
+ {
+ m_payments.erase(j);
+ break;
+ }
+ }
+
+ ++spent_txid;
+ }
+
+ for (size_t n : swept_transfers)
+ {
+ const transfer_details& td = m_transfers[n];
+ confirmed_transfer_details pd;
+ pd.m_change = (uint64_t)-1; // cahnge is unknown
+ pd.m_amount_in = pd.m_amount_out = td.amount(); // fee is unknown
+ std::string err;
+ pd.m_block_height = get_daemon_blockchain_height(err); // spent block height is unknown, so hypothetically set to the highest
+ crypto::hash spent_txid = crypto::rand<crypto::hash>(); // spent txid is unknown, so hypothetically set to random
+ m_confirmed_txs.insert(std::make_pair(spent_txid, pd));
+ }
+ }
+
return m_transfers[signed_key_images.size() - 1].m_block_height;
}
wallet2::payment_container wallet2::export_payments() const
@@ -5421,20 +6734,28 @@ void wallet2::import_payments_out(const std::list<std::pair<crypto::hash,wallet2
}
}
-std::vector<crypto::hash> wallet2::export_blockchain() const
+std::tuple<size_t,crypto::hash,std::vector<crypto::hash>> wallet2::export_blockchain() const
{
- std::vector<crypto::hash> bc;
- for (auto const &b : m_blockchain)
+ std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> bc;
+ std::get<0>(bc) = m_blockchain.offset();
+ std::get<1>(bc) = m_blockchain.empty() ? crypto::null_hash: m_blockchain.genesis();
+ for (size_t n = m_blockchain.offset(); n < m_blockchain.size(); ++n)
{
- bc.push_back(b);
+ std::get<2>(bc).push_back(m_blockchain[n]);
}
return bc;
}
-void wallet2::import_blockchain(const std::vector<crypto::hash> &bc)
+void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> &bc)
{
m_blockchain.clear();
- for (auto const &b : bc)
+ if (std::get<0>(bc))
+ {
+ for (size_t n = std::get<0>(bc); n > 0; ++n)
+ m_blockchain.push_back(std::get<1>(bc));
+ m_blockchain.trim(std::get<0>(bc));
+ }
+ for (auto const &b : std::get<2>(bc))
{
m_blockchain.push_back(b);
}
@@ -5471,14 +6792,17 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
// the hot wallet wouldn't have known about key images (except if we already exported them)
cryptonote::keypair in_ephemeral;
std::vector<tx_extra_field> tx_extra_fields;
- tx_extra_pub_key pub_key_field;
THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i));
THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(td.m_tx.extra, tx_extra_fields), error::wallet_internal_error,
"Transaction extra has unsupported format at index " + boost::lexical_cast<std::string>(i));
crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td);
+ const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
- cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, td.m_key_image);
+ const crypto::public_key& out_key = boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key;
+ bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image);
+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
+ expand_subaddresses(td.m_subaddr_index);
td.m_key_image_known = true;
THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key,
error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i));
@@ -5536,7 +6860,7 @@ std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret
crypto::secret_key_to_public_key(skey, pkey);
const crypto::signature &signature = *(const crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)];
THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature),
- error::wallet_internal_error, "Failed to authenticate criphertext");
+ error::wallet_internal_error, "Failed to authenticate ciphertext");
}
crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]);
return plaintext;
@@ -5549,17 +6873,15 @@ std::string wallet2::decrypt_with_view_secret_key(const std::string &ciphertext,
//----------------------------------------------------------------------------------------------------
std::string wallet2::make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error)
{
- cryptonote::account_public_address tmp_address;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if(!get_account_integrated_address_from_str(tmp_address, has_payment_id, new_payment_id, testnet(), address))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, testnet(), address))
{
error = std::string("wrong address: ") + address;
return std::string();
}
// we want only one payment id
- if (has_payment_id && !payment_id.empty())
+ if (info.has_payment_id && !payment_id.empty())
{
error = "A single payment id is allowed";
return std::string();
@@ -5615,10 +6937,8 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin
const char *ptr = strchr(remainder.c_str(), '?');
address = ptr ? remainder.substr(0, ptr-remainder.c_str()) : remainder;
- cryptonote::account_public_address addr;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
- if(!get_account_integrated_address_from_str(addr, has_payment_id, new_payment_id, testnet(), address))
+ cryptonote::address_parse_info info;
+ if(!get_account_address_from_str(info, testnet(), address))
{
error = std::string("URI has wrong address: ") + address;
return false;
@@ -5659,7 +6979,7 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin
}
else if (kv[0] == "tx_payment_id")
{
- if (has_payment_id)
+ if (info.has_payment_id)
{
error = "Separate payment id given with an integrated address";
return false;
@@ -5838,8 +7158,8 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(uint64_t mi
priority_size_max += i.blob_size;
}
- uint64_t nblocks_min = (priority_size_min + full_reward_zone - 1) / full_reward_zone;
- uint64_t nblocks_max = (priority_size_max + full_reward_zone - 1) / full_reward_zone;
+ uint64_t nblocks_min = priority_size_min / full_reward_zone;
+ uint64_t nblocks_max = priority_size_max / full_reward_zone;
MDEBUG("estimate_backlog: priority_size " << priority_size_min << " - " << priority_size_max << " for " << fee
<< " (" << our_fee_byte_min << " - " << our_fee_byte_max << " piconero byte fee), "
<< nblocks_min << " - " << nblocks_max << " blocks at block size " << full_reward_zone);
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index adf03abcc..746255fa9 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -36,6 +36,7 @@
#include <boost/program_options/variables_map.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/vector.hpp>
+#include <boost/serialization/deque.hpp>
#include <atomic>
#include "include_base_utils.h"
@@ -52,6 +53,7 @@
#include "crypto/hash.h"
#include "ringct/rctTypes.h"
#include "ringct/rctOps.h"
+#include "checkpoints/checkpoints.h"
#include "wallet_errors.h"
#include "common/password.h"
@@ -69,11 +71,19 @@ namespace tools
class i_wallet2_callback
{
public:
+ // Full wallet callbacks
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) {}
- virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) {}
- virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) {}
+ 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) {}
+ virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {}
+ virtual void 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) {}
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {}
+ // Light wallet callbacks
+ virtual void on_lw_new_block(uint64_t height) {}
+ virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
+ virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
+ virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
+ // Common callbacks
+ virtual void on_pool_tx_removed(const crypto::hash &txid) {}
virtual ~i_wallet2_callback() {}
};
@@ -91,6 +101,38 @@ namespace tools
}
};
+ class hashchain
+ {
+ public:
+ hashchain(): m_genesis(crypto::null_hash), m_offset(0) {}
+
+ size_t size() const { return m_blockchain.size() + m_offset; }
+ size_t offset() const { return m_offset; }
+ const crypto::hash &genesis() const { return m_genesis; }
+ void push_back(const crypto::hash &hash) { if (m_offset == 0 && m_blockchain.empty()) m_genesis = hash; m_blockchain.push_back(hash); }
+ bool is_in_bounds(size_t idx) const { return idx >= m_offset && idx < size(); }
+ const crypto::hash &operator[](size_t idx) const { return m_blockchain[idx - m_offset]; }
+ crypto::hash &operator[](size_t idx) { return m_blockchain[idx - m_offset]; }
+ void crop(size_t height) { m_blockchain.resize(height - m_offset); }
+ void clear() { m_offset = 0; m_blockchain.clear(); }
+ bool empty() const { return m_blockchain.empty() && m_offset == 0; }
+ void trim(size_t height) { while (height > m_offset && m_blockchain.size() > 1) { m_blockchain.pop_front(); ++m_offset; } m_blockchain.shrink_to_fit(); }
+ void refill(const crypto::hash &hash) { m_blockchain.push_back(hash); --m_offset; }
+
+ template <class t_archive>
+ inline void serialize(t_archive &a, const unsigned int ver)
+ {
+ a & m_offset;
+ a & m_genesis;
+ a & m_blockchain;
+ }
+
+ private:
+ size_t m_offset;
+ crypto::hash m_genesis;
+ std::deque<crypto::hash> m_blockchain;
+ };
+
class wallet2
{
friend class ::Serialization_portability_wallet_Test;
@@ -131,7 +173,20 @@ namespace tools
static bool verify_password(const std::string& keys_file_name, const std::string& password, bool watch_only);
- wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {}
+ wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {}
+
+ struct tx_scan_info_t
+ {
+ cryptonote::keypair in_ephemeral;
+ crypto::key_image ki;
+ rct::key mask;
+ uint64_t amount;
+ uint64_t money_transfered;
+ bool error;
+ boost::optional<cryptonote::subaddress_receive_info> received;
+
+ tx_scan_info_t(): money_transfered(0), error(true) {}
+ };
struct transfer_details
{
@@ -148,6 +203,7 @@ namespace tools
bool m_rct;
bool m_key_image_known;
size_t m_pk_index;
+ cryptonote::subaddress_index m_subaddr_index;
bool is_rct() const { return m_rct; }
uint64_t amount() const { return m_amount; }
@@ -167,6 +223,7 @@ namespace tools
FIELD(m_rct)
FIELD(m_key_image_known)
FIELD(m_pk_index)
+ FIELD(m_subaddr_index)
END_SERIALIZE()
};
@@ -177,6 +234,14 @@ namespace tools
uint64_t m_block_height;
uint64_t m_unlock_time;
uint64_t m_timestamp;
+ cryptonote::subaddress_index m_subaddr_index;
+ };
+
+ struct address_tx : payment_details
+ {
+ bool m_coinbase;
+ bool m_mempool;
+ bool m_incoming;
};
struct unconfirmed_transfer_details
@@ -190,6 +255,8 @@ namespace tools
crypto::hash m_payment_id;
enum { pending, pending_not_in_pool, failed } m_state;
uint64_t m_timestamp;
+ uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer
+ std::set<uint32_t> m_subaddr_indices; // set of address indices used as inputs in this transfer
};
struct confirmed_transfer_details
@@ -202,10 +269,12 @@ namespace tools
crypto::hash m_payment_id;
uint64_t m_timestamp;
uint64_t m_unlock_time;
+ uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer
+ std::set<uint32_t> m_subaddr_indices; // set of address indices used as inputs in this transfer
- confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(cryptonote::null_hash), m_timestamp(0), m_unlock_time(0) {}
+ confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(crypto::null_hash), m_timestamp(0), m_unlock_time(0), m_subaddr_account((uint32_t)-1) {}
confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height):
- m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time) {}
+ m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices) {}
};
struct tx_construction_data
@@ -218,6 +287,8 @@ namespace tools
uint64_t unlock_time;
bool use_rct;
std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change
+ uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer
+ std::set<uint32_t> subaddr_indices; // set of address indices used as inputs in this transfer
};
typedef std::vector<transfer_details> transfer_container;
@@ -235,6 +306,7 @@ namespace tools
std::list<size_t> selected_transfers;
std::string key_images;
crypto::secret_key tx_key;
+ std::vector<crypto::secret_key> additional_tx_keys;
std::vector<cryptonote::tx_destination_entry> dests;
tx_construction_data construction_data;
@@ -282,6 +354,7 @@ namespace tools
cryptonote::account_public_address m_address;
crypto::hash m_payment_id;
std::string m_description;
+ bool m_is_subaddress;
};
typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry;
@@ -352,7 +425,7 @@ namespace tools
// the minimum block size.
bool deinit();
bool init(std::string daemon_address = "http://localhost:8080",
- boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_size_limit = 0);
+ boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_size_limit = 0, bool ssl = false);
void stop() { m_run.store(false, std::memory_order_relaxed); }
@@ -363,7 +436,16 @@ namespace tools
* \brief Checks if deterministic wallet
*/
bool is_deterministic() const;
- bool get_seed(std::string& electrum_words) const;
+ bool get_seed(std::string& electrum_words, const std::string &passphrase = std::string()) const;
+
+ /*!
+ * \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned.
+ */
+ bool light_wallet() const { return m_light_wallet; }
+ void set_light_wallet(bool light_wallet) { m_light_wallet = light_wallet; }
+ uint64_t get_light_wallet_scanned_block_height() const { return m_light_wallet_scanned_block_height; }
+ uint64_t get_light_wallet_blockchain_height() const { return m_light_wallet_blockchain_height; }
+
/*!
* \brief Gets the seed language
*/
@@ -372,6 +454,21 @@ namespace tools
* \brief Sets the seed language
*/
void set_seed_language(const std::string &language);
+
+ // Subaddress scheme
+ cryptonote::account_public_address get_subaddress(const cryptonote::subaddress_index& index) const;
+ cryptonote::account_public_address get_address() const { return get_subaddress({0,0}); }
+ crypto::public_key get_subaddress_spend_public_key(const cryptonote::subaddress_index& index) const;
+ std::string get_subaddress_as_str(const cryptonote::subaddress_index& index) const;
+ std::string get_address_as_str() const { return get_subaddress_as_str({0, 0}); }
+ std::string get_integrated_address_as_str(const crypto::hash8& payment_id) const;
+ void add_subaddress_account(const std::string& label);
+ size_t get_num_subaddress_accounts() const { return m_subaddress_labels.size(); }
+ size_t get_num_subaddresses(uint32_t index_major) const { return index_major < m_subaddress_labels.size() ? m_subaddress_labels[index_major].size() : 0; }
+ void add_subaddress(uint32_t index_major, const std::string& label); // throws when index is out of bound
+ void expand_subaddresses(const cryptonote::subaddress_index& index);
+ std::string get_subaddress_label(const cryptonote::subaddress_index& index) const;
+ void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label);
/*!
* \brief Tells if the wallet file is deprecated.
*/
@@ -388,9 +485,15 @@ namespace tools
bool restricted() const { return m_restricted; }
bool watch_only() const { return m_watch_only; }
- uint64_t balance() const;
- uint64_t unlocked_balance() const;
- uint64_t unlocked_dust_balance(const tx_dust_policy &dust_policy) const;
+ // locked & unlocked balance of given or current subaddress account
+ uint64_t balance(uint32_t subaddr_index_major) const;
+ uint64_t unlocked_balance(uint32_t subaddr_index_major) const;
+ // locked & unlocked balance per subaddress of given or current subaddress account
+ std::map<uint32_t, uint64_t> balance_per_subaddress(uint32_t subaddr_index_major) const;
+ std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress(uint32_t subaddr_index_major) const;
+ // all locked & unlocked balances of all subaddress accounts
+ uint64_t balance_all() const;
+ uint64_t unlocked_balance_all() const;
template<typename T>
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, bool trusted_daemon);
template<typename T>
@@ -398,10 +501,10 @@ namespace tools
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, bool trusted_daemon);
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon);
template<typename T>
- void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count,
+ void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx);
- void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count,
+ void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx);
@@ -409,37 +512,50 @@ namespace tools
void commit_tx(std::vector<pending_tx>& ptx_vector);
bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename);
// load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet
- bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL);
+ bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL, bool export_raw = false);
// sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI
- bool sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx);
+ bool sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, bool export_raw = false);
// load unsigned_tx_set from file.
bool load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs);
bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL);
- std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon);
- std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon);
- std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon);
- std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon);
+ std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
+ std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); // pass subaddr_indices by value on purpose
+ std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon);
+ std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon);
bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000);
void get_transfers(wallet2::transfer_container& incoming_transfers) const;
- void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0) const;
- void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1) const;
+ void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
+ void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
void get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments,
- uint64_t min_height, uint64_t max_height = (uint64_t)-1) const;
- void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments) const;
- void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments) const;
+ uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
+ void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
+ void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
uint64_t get_blockchain_current_height() const { return m_local_bc_height; }
void rescan_spent();
void rescan_blockchain(bool refresh = true);
bool is_transfer_unlocked(const transfer_details& td) const;
+ bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const;
template <class t_archive>
inline void serialize(t_archive &a, const unsigned int ver)
{
uint64_t dummy_refresh_height = 0; // moved to keys file
if(ver < 5)
return;
- a & m_blockchain;
+ if (ver < 19)
+ {
+ std::vector<crypto::hash> blockchain;
+ a & blockchain;
+ for (const auto &b: blockchain)
+ {
+ m_blockchain.push_back(b);
+ }
+ }
+ else
+ {
+ a & m_blockchain;
+ }
a & m_transfers;
a & m_account_public_address;
a & m_key_images;
@@ -496,6 +612,12 @@ namespace tools
return;
a & m_scanned_pool_txs[0];
a & m_scanned_pool_txs[1];
+ if (ver < 20)
+ return;
+ a & m_subaddresses;
+ a & m_subaddresses_inv;
+ a & m_subaddress_labels;
+ a & m_additional_tx_keys;
}
/*!
@@ -541,14 +663,16 @@ namespace tools
bool merge_destinations() const { return m_merge_destinations; }
bool confirm_backlog() const { return m_confirm_backlog; }
void confirm_backlog(bool always) { m_confirm_backlog = always; }
+ void set_confirm_backlog_threshold(uint32_t threshold) { m_confirm_backlog_threshold = threshold; };
+ uint32_t get_confirm_backlog_threshold() const { return m_confirm_backlog_threshold; };
- bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const;
+ bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
/*!
* \brief GUI Address book get/store
*/
std::vector<address_book_row> get_address_book() const { return m_address_book; }
- bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description);
+ bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress);
bool delete_address_book_row(std::size_t row_id);
uint64_t get_num_rct_outputs();
@@ -588,14 +712,15 @@ namespace tools
payment_container export_payments() const;
void import_payments(const payment_container &payments);
void import_payments_out(const std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>> &confirmed_payments);
- std::vector<crypto::hash> export_blockchain() const;
- void import_blockchain(const std::vector<crypto::hash> &bc);
- bool export_key_images(const std::string filename);
+ 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);
std::vector<std::pair<crypto::key_image, crypto::signature>> export_key_images() const;
uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent, bool check_spent = true);
uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent);
void update_pool_state(bool refreshed = false);
+ void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes);
std::string encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const;
std::string encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated = true) const;
@@ -614,6 +739,24 @@ namespace tools
uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1);
uint64_t get_per_kb_fee();
+ // Light wallet specific functions
+ // fetch unspent outs from lw node and store in m_transfers
+ void light_wallet_get_unspent_outs();
+ // fetch txs and store in m_payments
+ void light_wallet_get_address_txs();
+ // get_address_info
+ bool light_wallet_get_address_info(cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response &response);
+ // Login. new_address is true if address hasn't been used on lw node before.
+ bool light_wallet_login(bool &new_address);
+ // Send an import request to lw node. returns info about import fee, address and payment_id
+ bool light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response);
+ // get random outputs from light wallet server
+ void light_wallet_get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count);
+ // Parse rct string
+ bool light_wallet_parse_rct_str(const std::string& rct_string, const crypto::public_key& tx_pub_key, uint64_t internal_output_index, rct::key& decrypted_mask, rct::key& rct_commit, bool decrypt) const;
+ // check if key image is ours
+ bool light_wallet_key_image_is_ours(const crypto::key_image& key_image, const crypto::public_key& tx_public_key, uint64_t out_index);
+
private:
/*!
* \brief Stores wallet information to wallet file.
@@ -643,27 +786,30 @@ namespace tools
uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon);
bool prepare_file_names(const std::string& file_path);
void process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height);
- void process_outgoing(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received);
- void add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount);
+ void process_outgoing(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices);
+ void add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices);
void generate_genesis(cryptonote::block& b);
void check_genesis(const crypto::hash& genesis_hash) const; //throws
bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const;
crypto::hash get_payment_id(const pending_tx &ptx) const;
crypto::hash8 get_short_payment_id(const pending_tx &ptx) const;
- void check_acc_out_precomp(const crypto::public_key &spend_public_key, const cryptonote::tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const;
+ void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const;
void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const;
uint64_t get_upper_transaction_size_limit();
std::vector<uint64_t> get_unspent_amounts_vector();
uint64_t get_dynamic_per_kb_fee_estimate();
float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const;
- std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money) const;
+ std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const;
void set_spent(size_t idx, uint64_t height);
void set_unspent(size_t idx);
void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count);
+ bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const;
bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki);
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const;
std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const;
+ void scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs);
+ void trim_hashchain();
cryptonote::account_base m_account;
boost::optional<epee::net_utils::http::login> m_daemon_login;
@@ -671,18 +817,23 @@ namespace tools
std::string m_wallet_file;
std::string m_keys_file;
epee::net_utils::http::http_simple_client m_http_client;
- std::vector<crypto::hash> m_blockchain;
+ hashchain m_blockchain;
std::atomic<uint64_t> m_local_bc_height; //temporary workaround
std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs;
std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs;
std::unordered_multimap<crypto::hash, payment_details> m_unconfirmed_payments;
std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys;
+ cryptonote::checkpoints m_checkpoints;
+ std::unordered_map<crypto::hash, std::vector<crypto::secret_key>> m_additional_tx_keys;
transfer_container m_transfers;
payment_container m_payments;
std::unordered_map<crypto::key_image, size_t> m_key_images;
std::unordered_map<crypto::public_key, size_t> m_pub_keys;
cryptonote::account_public_address m_account_public_address;
+ std::unordered_map<crypto::public_key, cryptonote::subaddress_index> m_subaddresses;
+ std::unordered_map<cryptonote::subaddress_index, crypto::public_key> m_subaddresses_inv;
+ std::vector<std::vector<std::string>> m_subaddress_labels;
std::unordered_map<crypto::hash, std::string> m_tx_notes;
std::vector<tools::wallet2::address_book_row> m_address_book;
uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value
@@ -711,21 +862,36 @@ namespace tools
uint64_t m_min_output_value;
bool m_merge_destinations;
bool m_confirm_backlog;
+ uint32_t m_confirm_backlog_threshold;
bool m_is_initialized;
NodeRPCProxy m_node_rpc_proxy;
std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
+
+ // Light wallet
+ bool m_light_wallet; /* sends view key to daemon for scanning */
+ uint64_t m_light_wallet_scanned_block_height;
+ uint64_t m_light_wallet_blockchain_height;
+ uint64_t m_light_wallet_per_kb_fee = FEE_PER_KB;
+ bool m_light_wallet_connected;
+ uint64_t m_light_wallet_balance;
+ uint64_t m_light_wallet_unlocked_balance;
+ // Light wallet info needed to populate m_payment requires 2 separate api calls (get_address_txs and get_unspent_outs)
+ // We save the info from the first call in m_light_wallet_address_txs for easier lookup.
+ std::unordered_map<crypto::hash, address_tx> m_light_wallet_address_txs;
+ // store calculated key image for faster lookup
+ std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> > m_key_image_cache;
};
}
-BOOST_CLASS_VERSION(tools::wallet2, 18)
-BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 7)
-BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1)
-BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 6)
-BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 4)
-BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 16)
+BOOST_CLASS_VERSION(tools::wallet2, 20)
+BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8)
+BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2)
+BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7)
+BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 5)
+BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0)
BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0)
-BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 0)
-BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 0)
+BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 1)
+BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 1)
namespace boost
{
@@ -759,6 +925,10 @@ namespace boost
{
x.m_pk_index = 0;
}
+ if (ver < 8)
+ {
+ x.m_subaddr_index = {};
+ }
}
template <class Archive>
@@ -826,6 +996,12 @@ namespace boost
return;
}
a & x.m_pk_index;
+ if (ver < 8)
+ {
+ initialize_transfer_details(a, x, ver);
+ return;
+ }
+ a & x.m_subaddr_index;
}
template <class Archive>
@@ -865,6 +1041,13 @@ namespace boost
if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1)
x.m_amount_out += x.m_change;
}
+ if (ver < 7)
+ {
+ x.m_subaddr_account = 0;
+ return;
+ }
+ a & x.m_subaddr_account;
+ a & x.m_subaddr_indices;
}
template <class Archive>
@@ -902,6 +1085,13 @@ namespace boost
return;
}
a & x.m_unlock_time;
+ if (ver < 5)
+ {
+ x.m_subaddr_account = 0;
+ return;
+ }
+ a & x.m_subaddr_account;
+ a & x.m_subaddr_indices;
}
template <class Archive>
@@ -914,13 +1104,12 @@ namespace boost
if (ver < 1)
return;
a & x.m_timestamp;
- }
-
- template <class Archive>
- inline void serialize(Archive& a, cryptonote::tx_destination_entry& x, const boost::serialization::version_type ver)
- {
- a & x.amount;
- a & x.addr;
+ if (ver < 2)
+ {
+ x.m_subaddr_index = {};
+ return;
+ }
+ a & x.m_subaddr_index;
}
template <class Archive>
@@ -929,6 +1118,12 @@ namespace boost
a & x.m_address;
a & x.m_payment_id;
a & x.m_description;
+ if (ver < 17)
+ {
+ x.m_is_subaddress = false;
+ return;
+ }
+ a & x.m_is_subaddress;
}
template <class Archive>
@@ -956,6 +1151,13 @@ namespace boost
a & x.unlock_time;
a & x.use_rct;
a & x.dests;
+ if (ver < 1)
+ {
+ x.subaddr_account = 0;
+ return;
+ }
+ a & x.subaddr_account;
+ a & x.subaddr_indices;
}
template <class Archive>
@@ -971,6 +1173,9 @@ namespace boost
a & x.tx_key;
a & x.dests;
a & x.construction_data;
+ if (ver < 1)
+ return;
+ a & x.additional_tx_keys;
}
}
}
@@ -991,18 +1196,18 @@ namespace tools
for(auto& de: dsts)
{
cryptonote::decompose_amount_into_digits(de.amount, 0,
- [&](uint64_t chunk) { splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, de.addr)); },
- [&](uint64_t a_dust) { splitted_dsts.push_back(cryptonote::tx_destination_entry(a_dust, de.addr)); } );
+ [&](uint64_t chunk) { splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, de.addr, de.is_subaddress)); },
+ [&](uint64_t a_dust) { splitted_dsts.push_back(cryptonote::tx_destination_entry(a_dust, de.addr, de.is_subaddress)); } );
}
cryptonote::decompose_amount_into_digits(change_dst.amount, 0,
[&](uint64_t chunk) {
if (chunk <= dust_threshold)
- dust_dsts.push_back(cryptonote::tx_destination_entry(chunk, change_dst.addr));
+ dust_dsts.push_back(cryptonote::tx_destination_entry(chunk, change_dst.addr, false));
else
- splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, change_dst.addr));
+ splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, change_dst.addr, false));
},
- [&](uint64_t a_dust) { dust_dsts.push_back(cryptonote::tx_destination_entry(a_dust, change_dst.addr)); } );
+ [&](uint64_t a_dust) { dust_dsts.push_back(cryptonote::tx_destination_entry(a_dust, change_dst.addr, false)); } );
}
//----------------------------------------------------------------------------------------------------
inline void null_split_strategy(const std::vector<cryptonote::tx_destination_entry>& dsts,
@@ -1016,7 +1221,7 @@ namespace tools
if (0 != change)
{
- splitted_dsts.push_back(cryptonote::tx_destination_entry(change, change_dst.addr));
+ splitted_dsts.push_back(cryptonote::tx_destination_entry(change, change_dst.addr, false));
}
}
//----------------------------------------------------------------------------------------------------
@@ -1039,7 +1244,7 @@ namespace tools
}
template<typename T>
- void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices,
+ void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, bool trusted_daemon)
{
using namespace cryptonote;
@@ -1064,6 +1269,10 @@ namespace tools
uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers, trusted_daemon);
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
+ uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major;
+ for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i)
+ THROW_WALLET_EXCEPTION_IF(subaddr_account != *i, error::wallet_internal_error, "the tx uses funds from multiple accounts");
+
typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry;
typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
@@ -1151,7 +1360,7 @@ namespace tools
cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts);
if (needed_money < found_money)
{
- change_dts.addr = m_account.get_keys().m_account_address;
+ change_dts.addr = get_subaddress({subaddr_account, 0});
change_dts.amount = found_money - needed_money;
}
@@ -1164,12 +1373,13 @@ namespace tools
}
for(auto& d: dust_dsts) {
if (!dust_policy.add_to_fee)
- splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust));
+ splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust, d.is_subaddress));
dust += d.amount;
}
crypto::secret_key tx_key;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key);
+ std::vector<crypto::secret_key> additional_tx_keys;
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet);
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
@@ -1195,6 +1405,7 @@ namespace tools
ptx.change_dts = change_dts;
ptx.selected_transfers = selected_transfers;
ptx.tx_key = tx_key;
+ ptx.additional_tx_keys = additional_tx_keys;
ptx.dests = dsts;
ptx.construction_data.sources = sources;
ptx.construction_data.change_dts = change_dts;
@@ -1204,6 +1415,11 @@ namespace tools
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = false;
ptx.construction_data.dests = dsts;
+ // record which subaddress indices are being used as inputs
+ ptx.construction_data.subaddr_account = subaddr_account;
+ ptx.construction_data.subaddr_indices.clear();
+ for (size_t idx: selected_transfers)
+ ptx.construction_data.subaddr_indices.insert(m_transfers[idx].m_subaddr_index.minor);
}
diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h
index 8da8c62eb..a8c150ca7 100644
--- a/src/wallet/wallet2_api.h
+++ b/src/wallet/wallet2_api.h
@@ -33,6 +33,7 @@
#include <string>
#include <vector>
+#include <set>
#include <ctime>
#include <iostream>
@@ -88,6 +89,8 @@ struct PendingTransaction
* \return
*/
virtual uint64_t txCount() const = 0;
+ virtual std::vector<uint32_t> subaddrAccount() const = 0;
+ virtual std::vector<std::set<uint32_t>> subaddrIndices() const = 0;
};
/**
@@ -155,6 +158,9 @@ struct TransactionInfo
virtual uint64_t amount() const = 0;
virtual uint64_t fee() const = 0;
virtual uint64_t blockHeight() const = 0;
+ virtual std::set<uint32_t> subaddrIndex() const = 0;
+ virtual uint32_t subaddrAccount() const = 0;
+ virtual std::string label() const = 0;
virtual uint64_t confirmations() const = 0;
virtual uint64_t unlockTime() const = 0;
//! transaction_id
@@ -223,6 +229,66 @@ struct AddressBook
virtual int lookupPaymentID(const std::string &payment_id) const = 0;
};
+struct SubaddressRow {
+public:
+ SubaddressRow(std::size_t _rowId, const std::string &_address, const std::string &_label):
+ m_rowId(_rowId),
+ m_address(_address),
+ m_label(_label) {}
+
+private:
+ std::size_t m_rowId;
+ std::string m_address;
+ std::string m_label;
+public:
+ std::string extra;
+ std::string getAddress() const {return m_address;}
+ std::string getLabel() const {return m_label;}
+ std::size_t getRowId() const {return m_rowId;}
+};
+
+struct Subaddress
+{
+ virtual ~Subaddress() = 0;
+ virtual std::vector<SubaddressRow*> getAll() const = 0;
+ virtual void addRow(uint32_t accountIndex, const std::string &label) = 0;
+ virtual void setLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0;
+ virtual void refresh(uint32_t accountIndex) = 0;
+};
+
+struct SubaddressAccountRow {
+public:
+ SubaddressAccountRow(std::size_t _rowId, const std::string &_address, const std::string &_label, const std::string &_balance, const std::string &_unlockedBalance):
+ m_rowId(_rowId),
+ m_address(_address),
+ m_label(_label),
+ m_balance(_balance),
+ m_unlockedBalance(_unlockedBalance) {}
+
+private:
+ std::size_t m_rowId;
+ std::string m_address;
+ std::string m_label;
+ std::string m_balance;
+ std::string m_unlockedBalance;
+public:
+ std::string extra;
+ std::string getAddress() const {return m_address;}
+ std::string getLabel() const {return m_label;}
+ std::string getBalance() const {return m_balance;}
+ std::string getUnlockedBalance() const {return m_unlockedBalance;}
+ std::size_t getRowId() const {return m_rowId;}
+};
+
+struct SubaddressAccount
+{
+ virtual ~SubaddressAccount() = 0;
+ virtual std::vector<SubaddressAccountRow*> getAll() const = 0;
+ virtual void addRow(const std::string &label) = 0;
+ virtual void setLabel(uint32_t accountIndex, const std::string &label) = 0;
+ virtual void refresh() = 0;
+};
+
struct WalletListener
{
virtual ~WalletListener() = 0;
@@ -294,7 +360,8 @@ struct Wallet
//! in case error status, returns error string
virtual std::string errorString() const = 0;
virtual bool setPassword(const std::string &password) = 0;
- virtual std::string address() const = 0;
+ virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const = 0;
+ std::string mainAddress() const { return address(0, 0); }
virtual std::string path() const = 0;
virtual bool testnet() const = 0;
//! returns current hard fork info
@@ -360,9 +427,12 @@ struct Wallet
*
* \param daemon_address - daemon address in "hostname:port" format
* \param upper_transaction_size_limit
+ * \param daemon_username
+ * \param daemon_password
+ * \param lightWallet - start wallet in light mode, connect to a openmonero compatible server.
* \return - true on success
*/
- virtual bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit, const std::string &daemon_username = "", const std::string &daemon_password = "") = 0;
+ virtual bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, const std::string &daemon_username = "", const std::string &daemon_password = "", bool use_ssl = false, bool lightWallet = false) = 0;
/*!
* \brief createWatchOnly - Creates a watch only wallet
@@ -406,8 +476,20 @@ struct Wallet
virtual ConnectionStatus connected() const = 0;
virtual void setTrustedDaemon(bool arg) = 0;
virtual bool trustedDaemon() const = 0;
- virtual uint64_t balance() const = 0;
- virtual uint64_t unlockedBalance() const = 0;
+ virtual uint64_t balance(uint32_t accountIndex = 0) const = 0;
+ uint64_t balanceAll() const {
+ uint64_t result = 0;
+ for (uint32_t i = 0; i < numSubaddressAccounts(); ++i)
+ result += balance(i);
+ return result;
+ }
+ virtual uint64_t unlockedBalance(uint32_t accountIndex = 0) const = 0;
+ uint64_t unlockedBalanceAll() const {
+ uint64_t result = 0;
+ for (uint32_t i = 0; i < numSubaddressAccounts(); ++i)
+ result += unlockedBalance(i);
+ return result;
+ }
/**
* @brief watchOnly - checks if wallet is watch only
@@ -492,6 +574,39 @@ struct Wallet
*/
virtual int autoRefreshInterval() const = 0;
+ /**
+ * @brief addSubaddressAccount - appends a new subaddress account at the end of the last major index of existing subaddress accounts
+ * @param label - the label for the new account (which is the as the label of the primary address (accountIndex,0))
+ */
+ virtual void addSubaddressAccount(const std::string& label) = 0;
+ /**
+ * @brief numSubaddressAccounts - returns the number of existing subaddress accounts
+ */
+ virtual size_t numSubaddressAccounts() const = 0;
+ /**
+ * @brief numSubaddresses - returns the number of existing subaddresses associated with the specified subaddress account
+ * @param accountIndex - the major index specifying the subaddress account
+ */
+ virtual size_t numSubaddresses(uint32_t accountIndex) const = 0;
+ /**
+ * @brief addSubaddress - appends a new subaddress at the end of the last minor index of the specified subaddress account
+ * @param accountIndex - the major index specifying the subaddress account
+ * @param label - the label for the new subaddress
+ */
+ virtual void addSubaddress(uint32_t accountIndex, const std::string& label) = 0;
+ /**
+ * @brief getSubaddressLabel - gets the label of the specified subaddress
+ * @param accountIndex - the major index specifying the subaddress account
+ * @param addressIndex - the minor index specifying the subaddress
+ */
+ virtual std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const = 0;
+ /**
+ * @brief setSubaddressLabel - sets the label of the specified subaddress
+ * @param accountIndex - the major index specifying the subaddress account
+ * @param addressIndex - the minor index specifying the subaddress
+ * @param label - the new label for the specified subaddress
+ */
+ virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0;
/*!
* \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored
@@ -499,6 +614,8 @@ struct Wallet
* \param payment_id optional payment_id, can be empty string
* \param amount amount
* \param mixin_count mixin count. if 0 passed, wallet will use default value
+ * \param subaddr_account subaddress account from which the input funds are taken
+ * \param subaddr_indices set of subaddress indices to use for transfer or sweeping. if set empty, all are chosen when sweeping, and one or more are automatically chosen when transferring. after execution, returns the set of actually used indices
* \param priority
* \return PendingTransaction object. caller is responsible to check PendingTransaction::status()
* after object returned
@@ -506,7 +623,9 @@ struct Wallet
virtual PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id,
optional<uint64_t> amount, uint32_t mixin_count,
- PendingTransaction::Priority = PendingTransaction::Priority_Low) = 0;
+ PendingTransaction::Priority = PendingTransaction::Priority_Low,
+ uint32_t subaddr_account = 0,
+ std::set<uint32_t> subaddr_indices = {}) = 0;
/*!
* \brief createSweepUnmixableTransaction creates transaction with unmixable outputs.
@@ -551,8 +670,10 @@ struct Wallet
virtual bool importKeyImages(const std::string &filename) = 0;
- virtual TransactionHistory * history() const = 0;
- virtual AddressBook * addressBook() const = 0;
+ virtual TransactionHistory * history() = 0;
+ virtual AddressBook * addressBook() = 0;
+ virtual Subaddress * subaddress() = 0;
+ virtual SubaddressAccount * subaddressAccount() = 0;
virtual void setListener(WalletListener *) = 0;
/*!
* \brief defaultMixin - returns number of mixins used in transactions
@@ -604,6 +725,12 @@ struct Wallet
* \return true on success
*/
virtual bool rescanSpent() = 0;
+
+ //! Light wallet authenticate and login
+ virtual bool lightWalletLogin(bool &isNewWallet) const = 0;
+
+ //! Initiates a light wallet import wallet request
+ virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) = 0;
};
/**
@@ -663,7 +790,7 @@ struct WalletManager
* \param wallet previously opened / created wallet instance
* \return None
*/
- virtual bool closeWallet(Wallet *wallet) = 0;
+ virtual bool closeWallet(Wallet *wallet, bool store = true) = 0;
/*
* ! checks if wallet with the given name already exists
diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp
index 34c5a2a5d..df01ec238 100644
--- a/src/wallet/wallet_args.cpp
+++ b/src/wallet/wallet_args.cpp
@@ -84,13 +84,14 @@ namespace wallet_args
#endif
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
+ const command_line::arg_descriptor<std::size_t> arg_max_log_file_size = {"max-log-file-size", "Specify maximum log file size [B]", MAX_LOG_FILE_SIZE};
const command_line::arg_descriptor<uint32_t> arg_max_concurrency = {"max-concurrency", wallet_args::tr("Max number of threads to use for a parallel job"), DEFAULT_MAX_CONCURRENCY};
const command_line::arg_descriptor<std::string> arg_log_file = {"log-file", wallet_args::tr("Specify log file"), ""};
const command_line::arg_descriptor<std::string> arg_config_file = {"config-file", wallet_args::tr("Config file"), "", true};
std::string lang = i18n_get_language();
- tools::sanitize_locale();
+ tools::on_startup();
tools::set_strict_default_file_permissions(true);
epee::string_tools::set_module_name_and_folder(argv[0]);
@@ -99,8 +100,9 @@ namespace wallet_args
command_line::add_arg(desc_general, command_line::arg_help);
command_line::add_arg(desc_general, command_line::arg_version);
- command_line::add_arg(desc_params, arg_log_file, "");
+ command_line::add_arg(desc_params, arg_log_file);
command_line::add_arg(desc_params, arg_log_level);
+ command_line::add_arg(desc_params, arg_max_log_file_size);
command_line::add_arg(desc_params, arg_max_concurrency);
command_line::add_arg(desc_params, arg_config_file);
@@ -114,6 +116,21 @@ namespace wallet_args
auto parser = po::command_line_parser(argc, argv).options(desc_all).positional(positional_options);
po::store(parser.run(), vm);
+ if (command_line::get_arg(vm, command_line::arg_help))
+ {
+ tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL;
+ tools::msg_writer() << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n"
+ "daemon to work correctly.") << ENDL;
+ tools::msg_writer() << wallet_args::tr("Usage:") << ENDL << " " << usage;
+ tools::msg_writer() << desc_all;
+ return false;
+ }
+ else if (command_line::get_arg(vm, command_line::arg_version))
+ {
+ tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")";
+ return false;
+ }
+
if(command_line::has_arg(vm, arg_config_file))
{
std::string config = command_line::get_arg(vm, arg_config_file);
@@ -137,37 +154,22 @@ namespace wallet_args
return boost::none;
std::string log_path;
- if (!vm["log-file"].defaulted())
+ if (!command_line::is_arg_defaulted(vm, arg_log_file))
log_path = command_line::get_arg(vm, arg_log_file);
else
log_path = mlog_get_default_log_path(default_log_name);
- mlog_configure(log_path, log_to_console);
- if (!vm["log-level"].defaulted())
+ mlog_configure(log_path, log_to_console, command_line::get_arg(vm, arg_max_log_file_size));
+ if (!command_line::is_arg_defaulted(vm, arg_log_level))
{
mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str());
}
- if (command_line::get_arg(vm, command_line::arg_help))
- {
- tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL;
- tools::msg_writer() << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n"
- "daemon to work correctly.") << ENDL;
- tools::msg_writer() << wallet_args::tr("Usage:") << ENDL << " " << usage;
- tools::msg_writer() << desc_all;
- return boost::none;
- }
- else if (command_line::get_arg(vm, command_line::arg_version))
- {
- tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")";
- return boost::none;
- }
-
if(command_line::has_arg(vm, arg_max_concurrency))
tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency));
tools::scoped_message_writer(epee::console_color_white, true) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")";
- if (!vm["log-level"].defaulted())
+ if (!command_line::is_arg_defaulted(vm, arg_log_level))
MINFO("Setting log level = " << command_line::get_arg(vm, arg_log_level));
else
MINFO("Setting log levels = " << getenv("MONERO_LOGS"));
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index 2e5acc8cb..d1f4a796d 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -492,7 +492,7 @@ namespace tools
for (size_t i = 0; i < m_destinations.size(); ++i)
{
const cryptonote::tx_destination_entry& dst = m_destinations[i];
- ss << "\n " << i << ": " << cryptonote::get_account_address_as_str(m_testnet, dst.addr) << " " <<
+ ss << "\n " << i << ": " << cryptonote::get_account_address_as_str(m_testnet, dst.is_subaddress, dst.addr) << " " <<
cryptonote::print_money(dst.amount);
}
@@ -567,7 +567,7 @@ namespace tools
", destinations:";
for (const auto& dst : m_destinations)
{
- ss << '\n' << cryptonote::print_money(dst.amount) << " -> " << cryptonote::get_account_address_as_str(m_testnet, dst.addr);
+ ss << '\n' << cryptonote::print_money(dst.amount) << " -> " << cryptonote::get_account_address_as_str(m_testnet, dst.is_subaddress, dst.addr);
}
return ss.str();
}
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 773d12775..acd5357ed 100644..100755
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -27,6 +27,7 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+#include <boost/format.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/filesystem/operations.hpp>
#include <cstdint>
@@ -37,7 +38,6 @@ using namespace epee;
#include "wallet/wallet_args.h"
#include "common/command_line.h"
#include "common/i18n.h"
-#include "common/util.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_basic/account.h"
#include "wallet_rpc_server_commands_defs.h"
@@ -70,18 +70,12 @@ namespace tools
}
//------------------------------------------------------------------------------------------------------------------------------
- wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_filename(), m_stop(false), m_trusted_daemon(false)
+ wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_file(), m_stop(false), m_trusted_daemon(false), m_vm(NULL)
{
}
//------------------------------------------------------------------------------------------------------------------------------
wallet_rpc_server::~wallet_rpc_server()
{
- try
- {
- boost::system::error_code ec{};
- boost::filesystem::remove(rpc_login_filename, ec);
- }
- catch (...) {}
}
//------------------------------------------------------------------------------------------------------------------------------
void wallet_rpc_server::set_wallet(wallet2 *cr)
@@ -160,7 +154,15 @@ namespace tools
#else
#define MKDIR(path, mode) mkdir(path, mode)
#endif
- MKDIR(m_wallet_dir.c_str(), 0700);
+ if (!m_wallet_dir.empty() && MKDIR(m_wallet_dir.c_str(), 0700) < 0 && errno != EEXIST)
+ {
+#ifdef _WIN32
+ LOG_ERROR(tr("Failed to create directory ") + m_wallet_dir);
+#else
+ LOG_ERROR((boost::format(tr("Failed to create directory %s: %s")) % m_wallet_dir % strerror(errno)).str());
+#endif
+ return false;
+ }
}
if (disable_auth)
@@ -182,41 +184,39 @@ namespace tools
default_rpc_username,
string_encoding::base64_encode(rand_128bit.data(), rand_128bit.size())
);
+
+ std::string temp = "monero-wallet-rpc." + bind_port + ".login";
+ rpc_login_file = tools::private_file::create(temp);
+ if (!rpc_login_file.handle())
+ {
+ LOG_ERROR(tr("Failed to create file ") << temp << tr(". Check permissions or remove file"));
+ return false;
+ }
+ std::fputs(http_login->username.c_str(), rpc_login_file.handle());
+ std::fputc(':', rpc_login_file.handle());
+ std::fputs(http_login->password.c_str(), rpc_login_file.handle());
+ std::fflush(rpc_login_file.handle());
+ if (std::ferror(rpc_login_file.handle()))
+ {
+ LOG_ERROR(tr("Error writing to file ") << temp);
+ return false;
+ }
+ LOG_PRINT_L0(tr("RPC username/password is stored in file ") << temp);
}
- else
+ else // chosen user/pass
{
http_login.emplace(
std::move(rpc_config->login->username), std::move(rpc_config->login->password).password()
);
}
assert(bool(http_login));
-
- std::string temp = "monero-wallet-rpc." + bind_port + ".login";
- const auto cookie = tools::create_private_file(temp);
- if (!cookie)
- {
- LOG_ERROR(tr("Failed to create file ") << temp << tr(". Check permissions or remove file"));
- return false;
- }
- rpc_login_filename.swap(temp); // nothrow guarantee destructor cleanup
- temp = rpc_login_filename;
- std::fputs(http_login->username.c_str(), cookie.get());
- std::fputc(':', cookie.get());
- std::fputs(http_login->password.c_str(), cookie.get());
- std::fflush(cookie.get());
- if (std::ferror(cookie.get()))
- {
- LOG_ERROR(tr("Error writing to file ") << temp);
- return false;
- }
- LOG_PRINT_L0(tr("RPC username/password is stored in file ") << temp);
} // end auth enabled
m_http_client.set_server(walvars->get_daemon_address(), walvars->get_daemon_login());
m_net_server.set_threads_prefix("RPC");
return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init(
- std::move(bind_port), std::move(rpc_config->bind_ip), std::move(http_login)
+ std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login)
);
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -253,6 +253,7 @@ namespace tools
entry.fee = 0; // TODO
entry.note = m_wallet->get_tx_note(pd.m_tx_hash);
entry.type = "in";
+ entry.subaddr_index = pd.m_subaddr_index;
}
//------------------------------------------------------------------------------------------------------------------------------
void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd)
@@ -273,10 +274,11 @@ namespace tools
entry.destinations.push_back(wallet_rpc::transfer_destination());
wallet_rpc::transfer_destination &td = entry.destinations.back();
td.amount = d.amount;
- td.address = get_account_address_as_str(m_wallet->testnet(), d.addr);
+ td.address = get_account_address_as_str(m_wallet->testnet(), d.is_subaddress, d.addr);
}
entry.type = "out";
+ entry.subaddr_index = { pd.m_subaddr_account, 0 };
}
//------------------------------------------------------------------------------------------------------------------------------
void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd)
@@ -294,6 +296,7 @@ namespace tools
entry.unlock_time = pd.m_tx.unlock_time;
entry.note = m_wallet->get_tx_note(txid);
entry.type = is_failed ? "failed" : "pending";
+ entry.subaddr_index = { pd.m_subaddr_account, 0 };
}
//------------------------------------------------------------------------------------------------------------------------------
void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd)
@@ -309,6 +312,7 @@ namespace tools
entry.fee = 0; // TODO
entry.note = m_wallet->get_tx_note(pd.m_tx_hash);
entry.type = "pool";
+ entry.subaddr_index = pd.m_subaddr_index;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er)
@@ -316,13 +320,28 @@ namespace tools
if (!m_wallet) return not_open(er);
try
{
- res.balance = m_wallet->balance();
- res.unlocked_balance = m_wallet->unlocked_balance();
+ res.balance = m_wallet->balance(req.account_index);
+ res.unlocked_balance = m_wallet->unlocked_balance(req.account_index);
+ std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(req.account_index);
+ std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(req.account_index);
+ std::vector<tools::wallet2::transfer_details> transfers;
+ m_wallet->get_transfers(transfers);
+ for (const auto& i : balance_per_subaddress)
+ {
+ wallet_rpc::COMMAND_RPC_GET_BALANCE::per_subaddress_info info;
+ info.address_index = i.first;
+ cryptonote::subaddress_index index = {req.account_index, info.address_index};
+ info.address = m_wallet->get_subaddress_as_str(index);
+ info.balance = i.second;
+ info.unlocked_balance = unlocked_balance_per_subaddress[i.first];
+ info.label = m_wallet->get_subaddress_label(index);
+ info.num_unspent_outputs = std::count_if(transfers.begin(), transfers.end(), [&](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == index; });
+ res.per_subaddress.push_back(info);
+ }
}
catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = e.what();
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
@@ -333,12 +352,126 @@ namespace tools
if (!m_wallet) return not_open(er);
try
{
- res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet());
+ res.addresses.resize(m_wallet->get_num_subaddresses(req.account_index));
+ tools::wallet2::transfer_container transfers;
+ m_wallet->get_transfers(transfers);
+ cryptonote::subaddress_index index = {req.account_index, 0};
+ for (; index.minor < m_wallet->get_num_subaddresses(req.account_index); ++index.minor)
+ {
+ auto& info = res.addresses[index.minor];
+ info.address = m_wallet->get_subaddress_as_str(index);
+ info.label = m_wallet->get_subaddress_label(index);
+ info.address_index = index.minor;
+ info.used = std::find_if(transfers.begin(), transfers.end(), [&](const tools::wallet2::transfer_details& td) { return td.m_subaddr_index == index; }) != transfers.end();
+ }
+ res.address = m_wallet->get_subaddress_as_str({req.account_index, 0});
}
catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = e.what();
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ m_wallet->add_subaddress(req.account_index, req.label);
+ res.address_index = m_wallet->get_num_subaddresses(req.account_index) - 1;
+ res.address = m_wallet->get_subaddress_as_str({req.account_index, res.address_index});
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ if (req.index.major >= m_wallet->get_num_subaddress_accounts())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_ACCOUNT_INDEX_OUTOFBOUND;
+ er.message = "Account index is out of bound";
+ return false;
+ }
+ if (req.index.minor >= m_wallet->get_num_subaddresses(req.index.major))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_ADDRESS_INDEX_OUTOFBOUND;
+ er.message = "Address index is out of bound";
+ return false;
+ }
+ try
+ {
+ m_wallet->set_subaddress_label(req.index, req.label);
+ }
+ catch (const std::exception& e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_get_accounts(const wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ try
+ {
+ res.total_balance = 0;
+ res.total_unlocked_balance = 0;
+ cryptonote::subaddress_index subaddr_index = {0,0};
+ for (; subaddr_index.major < m_wallet->get_num_subaddress_accounts(); ++subaddr_index.major)
+ {
+ wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::subaddress_account_info info;
+ info.account_index = subaddr_index.major;
+ info.base_address = m_wallet->get_subaddress_as_str(subaddr_index);
+ info.balance = m_wallet->balance(subaddr_index.major);
+ info.unlocked_balance = m_wallet->unlocked_balance(subaddr_index.major);
+ info.label = m_wallet->get_subaddress_label(subaddr_index);
+ res.subaddress_accounts.push_back(info);
+ res.total_balance += info.balance;
+ res.total_unlocked_balance += info.unlocked_balance;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ try
+ {
+ m_wallet->add_subaddress_account(req.label);
+ res.account_index = m_wallet->get_num_subaddress_accounts() - 1;
+ res.address = m_wallet->get_subaddress_as_str({res.account_index, 0});
+ }
+ catch (const std::exception& e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ if (req.account_index >= m_wallet->get_num_subaddress_accounts())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_ACCOUNT_INDEX_OUTOFBOUND;
+ er.message = "Account index is out of bound";
+ return false;
+ }
+ try
+ {
+ m_wallet->set_subaddress_label({req.account_index, 0}, req.label);
+ }
+ catch (const std::exception& e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
@@ -353,24 +486,22 @@ namespace tools
}
catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = e.what();
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
- bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination> destinations, std::string payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er)
+ bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er)
{
- crypto::hash8 integrated_payment_id = cryptonote::null_hash8;
+ crypto::hash8 integrated_payment_id = crypto::null_hash8;
std::string extra_nonce;
for (auto it = destinations.begin(); it != destinations.end(); it++)
{
+ cryptonote::address_parse_info info;
cryptonote::tx_destination_entry de;
- bool has_payment_id;
- crypto::hash8 new_payment_id;
er.message = "";
- if(!get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), it->address,
+ if(!get_account_address_from_str_or_url(info, m_wallet->testnet(), it->address,
[&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string {
if (!dnssec_valid)
{
@@ -390,18 +521,21 @@ namespace tools
er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + it->address;
return false;
}
+
+ de.addr = info.address;
+ de.is_subaddress = info.is_subaddress;
de.amount = it->amount;
dsts.push_back(de);
- if (has_payment_id)
+ if (info.has_payment_id)
{
- if (!payment_id.empty() || integrated_payment_id != cryptonote::null_hash8)
+ if (!payment_id.empty() || integrated_payment_id != crypto::null_hash8)
{
er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
er.message = "A single payment id is allowed per transaction";
return false;
}
- integrated_payment_id = new_payment_id;
+ integrated_payment_id = info.payment_id;
cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, integrated_payment_id);
/* Append Payment ID data into extra */
@@ -472,12 +606,12 @@ namespace tools
try
{
uint64_t mixin = adjust_mixin(req.mixin);
- std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon);
+ std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
// reject proposed transactions if there are more than one. see on_transfer_split below.
if (ptx_vector.size() != 1)
{
- er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR;
+ er.code = WALLET_RPC_ERROR_CODE_TX_TOO_LARGE;
er.message = "Transaction would be too large. try /transfer_split.";
return false;
}
@@ -501,22 +635,9 @@ namespace tools
}
return true;
}
- catch (const tools::error::daemon_busy& e)
- {
- er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY;
- er.message = e.what();
- return false;
- }
catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR;
- er.message = e.what();
- return false;
- }
- catch (...)
- {
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR";
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR);
return false;
}
return true;
@@ -548,7 +669,7 @@ namespace tools
uint64_t ptx_amount;
std::vector<wallet2::pending_tx> ptx_vector;
LOG_PRINT_L2("on_transfer_split calling create_transactions_2");
- ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon);
+ ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
LOG_PRINT_L2("on_transfer_split called create_transactions_2");
if (!req.do_not_relay)
@@ -584,22 +705,9 @@ namespace tools
return true;
}
- catch (const tools::error::daemon_busy& e)
- {
- er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY;
- er.message = e.what();
- return false;
- }
catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR;
- er.message = e.what();
- return false;
- }
- catch (...)
- {
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR";
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR);
return false;
}
return true;
@@ -641,22 +749,9 @@ namespace tools
return true;
}
- catch (const tools::error::daemon_busy& e)
- {
- er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY;
- er.message = e.what();
- return false;
- }
catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR;
- er.message = e.what();
- return false;
- }
- catch (...)
- {
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR";
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR);
return false;
}
return true;
@@ -688,7 +783,7 @@ namespace tools
try
{
uint64_t mixin = adjust_mixin(req.mixin);
- std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon);
+ std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
if (!req.do_not_relay)
m_wallet->commit_tx(ptx_vector);
@@ -711,22 +806,9 @@ namespace tools
return true;
}
- catch (const tools::error::daemon_busy& e)
- {
- er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY;
- er.message = e.what();
- return false;
- }
catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR;
- er.message = e.what();
- return false;
- }
- catch (...)
- {
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR";
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR);
return false;
}
return true;
@@ -752,14 +834,13 @@ namespace tools
}
}
- res.integrated_address = m_wallet->get_account().get_public_integrated_address_str(payment_id, m_wallet->testnet());
+ res.integrated_address = m_wallet->get_integrated_address_as_str(payment_id);
res.payment_id = epee::string_tools::pod_to_hex(payment_id);
return true;
}
- catch (const std::exception &e)
+ catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = e.what();
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
@@ -770,30 +851,27 @@ namespace tools
if (!m_wallet) return not_open(er);
try
{
- cryptonote::account_public_address address;
- crypto::hash8 payment_id;
- bool has_payment_id;
+ cryptonote::address_parse_info info;
- if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), req.integrated_address))
+ if(!get_account_address_from_str(info, m_wallet->testnet(), req.integrated_address))
{
er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS;
er.message = "Invalid address";
return false;
}
- if(!has_payment_id)
+ if(!info.has_payment_id)
{
er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS;
er.message = "Address is not an integrated address";
return false;
}
- res.standard_address = get_account_address_as_str(m_wallet->testnet(),address);
- res.payment_id = epee::string_tools::pod_to_hex(payment_id);
+ res.standard_address = get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address);
+ res.payment_id = epee::string_tools::pod_to_hex(info.payment_id);
return true;
}
- catch (const std::exception &e)
+ catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = e.what();
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
@@ -815,8 +893,7 @@ namespace tools
}
catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = e.what();
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
@@ -863,6 +940,7 @@ namespace tools
rpc_payment.amount = payment.m_amount;
rpc_payment.block_height = payment.m_block_height;
rpc_payment.unlock_time = payment.m_unlock_time;
+ rpc_payment.subaddr_index = payment.m_subaddr_index;
res.payments.push_back(rpc_payment);
}
@@ -888,6 +966,7 @@ namespace tools
rpc_payment.amount = payment.second.m_amount;
rpc_payment.block_height = payment.second.m_block_height;
rpc_payment.unlock_time = payment.second.m_unlock_time;
+ rpc_payment.subaddr_index = payment.second.m_subaddr_index;
res.payments.push_back(std::move(rpc_payment));
}
@@ -937,6 +1016,7 @@ namespace tools
rpc_payment.amount = payment.m_amount;
rpc_payment.block_height = payment.m_block_height;
rpc_payment.unlock_time = payment.m_unlock_time;
+ rpc_payment.subaddr_index = payment.m_subaddr_index;
res.payments.push_back(std::move(rpc_payment));
}
}
@@ -975,6 +1055,8 @@ namespace tools
{
if (!filter || available != td.m_spent)
{
+ if (req.account_index != td.m_subaddr_index.major || (!req.subaddr_indices.empty() && req.subaddr_indices.count(td.m_subaddr_index.minor) == 0))
+ continue;
if (!transfers_found)
{
transfers_found = true;
@@ -986,6 +1068,7 @@ namespace tools
rpc_transfers.global_index = td.m_global_output_index;
rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(td.m_txid);
rpc_transfers.tx_size = txBlob.size();
+ rpc_transfers.subaddr_index = td.m_subaddr_index.minor;
res.transfers.push_back(rpc_transfers);
}
}
@@ -1040,8 +1123,7 @@ namespace tools
}
catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = e.what();
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
@@ -1071,11 +1153,9 @@ namespace tools
return false;
}
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 payment_id;
+ cryptonote::address_parse_info info;
er.message = "";
- if(!get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), req.address,
+ if(!get_account_address_from_str_or_url(info, m_wallet->testnet(), req.address,
[&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string {
if (!dnssec_valid)
{
@@ -1094,7 +1174,7 @@ namespace tools
return false;
}
- res.good = m_wallet->verify(req.data, address, req.signature);
+ res.good = m_wallet->verify(req.data, info.address, req.signature);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -1115,8 +1195,7 @@ namespace tools
}
catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = e.what();
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
@@ -1214,7 +1293,7 @@ namespace tools
if (req.in)
{
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
- m_wallet->get_payments(payments, min_height, max_height);
+ m_wallet->get_payments(payments, min_height, max_height, req.account_index, req.subaddr_indices);
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
res.in.push_back(wallet_rpc::transfer_entry());
fill_transfer_entry(res.in.back(), i->second.m_tx_hash, i->first, i->second);
@@ -1224,7 +1303,7 @@ namespace tools
if (req.out)
{
std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments;
- m_wallet->get_payments_out(payments, min_height, max_height);
+ m_wallet->get_payments_out(payments, min_height, max_height, req.account_index, req.subaddr_indices);
for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
res.out.push_back(wallet_rpc::transfer_entry());
fill_transfer_entry(res.out.back(), i->first, i->second);
@@ -1233,7 +1312,7 @@ namespace tools
if (req.pending || req.failed) {
std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments;
- m_wallet->get_unconfirmed_payments_out(upayments);
+ m_wallet->get_unconfirmed_payments_out(upayments, req.account_index, req.subaddr_indices);
for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
const tools::wallet2::unconfirmed_transfer_details &pd = i->second;
bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed;
@@ -1250,7 +1329,7 @@ namespace tools
m_wallet->update_pool_state();
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
- m_wallet->get_unconfirmed_payments(payments);
+ m_wallet->get_unconfirmed_payments(payments, req.account_index, req.subaddr_indices);
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
res.pool.push_back(wallet_rpc::transfer_entry());
fill_transfer_entry(res.pool.back(), i->first, i->second);
@@ -1351,10 +1430,9 @@ namespace tools
}
}
- catch (...)
+ catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = "Failed";
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
@@ -1407,10 +1485,9 @@ namespace tools
res.height = height;
}
- catch (...)
+ catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = "Failed";
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
@@ -1453,7 +1530,7 @@ namespace tools
{
uint64_t idx = 0;
for (const auto &entry: ab)
- res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx++, get_account_address_as_str(m_wallet->testnet(), entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description});
+ res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx++, get_account_address_as_str(m_wallet->testnet(), entry.m_is_subaddress, entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description});
}
else
{
@@ -1466,7 +1543,7 @@ namespace tools
return false;
}
const auto &entry = ab[idx];
- res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx, get_account_address_as_str(m_wallet->testnet(), entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description});
+ res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx, get_account_address_as_str(m_wallet->testnet(), entry.m_is_subaddress, entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description});
}
}
return true;
@@ -1482,12 +1559,10 @@ namespace tools
return false;
}
- cryptonote::account_public_address address;
- bool has_payment_id;
- crypto::hash8 payment_id8;
- crypto::hash payment_id = cryptonote::null_hash;
+ cryptonote::address_parse_info info;
+ crypto::hash payment_id = crypto::null_hash;
er.message = "";
- if(!get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), req.address,
+ if(!get_account_address_from_str_or_url(info, m_wallet->testnet(), req.address,
[&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string {
if (!dnssec_valid)
{
@@ -1507,14 +1582,14 @@ namespace tools
er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + req.address;
return false;
}
- if (has_payment_id)
+ if (info.has_payment_id)
{
- memcpy(payment_id.data, payment_id8.data, 8);
+ memcpy(payment_id.data, info.payment_id.data, 8);
memset(payment_id.data + 8, 0, 24);
}
if (!req.payment_id.empty())
{
- if (has_payment_id)
+ if (info.has_payment_id)
{
er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
er.message = "Separate payment ID given with integrated address";
@@ -1526,7 +1601,7 @@ namespace tools
if (!wallet2::parse_long_payment_id(req.payment_id, payment_id))
{
- if (!wallet2::parse_short_payment_id(req.payment_id, payment_id8))
+ if (!wallet2::parse_short_payment_id(req.payment_id, info.payment_id))
{
er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
er.message = "Payment id has invalid format: \"" + req.payment_id + "\", expected 16 or 64 character string";
@@ -1534,18 +1609,18 @@ namespace tools
}
else
{
- memcpy(payment_id.data, payment_id8.data, 8);
+ memcpy(payment_id.data, info.payment_id.data, 8);
memset(payment_id.data + 8, 0, 24);
}
}
}
- if (!m_wallet->add_address_book_row(address, payment_id, req.description))
+ if (!m_wallet->add_address_book_row(info.address, payment_id, req.description, info.is_subaddress))
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to add address book entry";
return false;
}
- res.index = m_wallet->get_address_book().size();
+ res.index = m_wallet->get_address_book().size() - 1;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -1589,10 +1664,9 @@ namespace tools
m_wallet->rescan_spent();
return true;
}
- catch (const std::exception &e)
+ catch (const std::exception& e)
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = e.what();
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
return false;
}
return true;
@@ -1657,7 +1731,7 @@ namespace tools
{
if (m_wallet_dir.empty())
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.code = WALLET_RPC_ERROR_CODE_NO_WALLET_DIR;
er.message = "No wallet dir configured";
return false;
}
@@ -1725,6 +1799,11 @@ namespace tools
}
catch (const std::exception& e)
{
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ if (!wal)
+ {
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to generate wallet";
return false;
@@ -1739,7 +1818,7 @@ namespace tools
{
if (m_wallet_dir.empty())
{
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.code = WALLET_RPC_ERROR_CODE_NO_WALLET_DIR;
er.message = "No wallet dir configured";
return false;
}
@@ -1773,13 +1852,13 @@ namespace tools
command_line::add_arg(desc, arg_password);
po::store(po::parse_command_line(argc, argv, desc), vm2);
}
- std::unique_ptr<tools::wallet2> wal;
+ std::unique_ptr<tools::wallet2> wal = nullptr;
try {
wal = tools::wallet2::make_from_file(vm2, wallet_file).first;
}
catch (const std::exception& e)
{
- wal = nullptr;
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
}
if (!wal)
{
@@ -1793,6 +1872,63 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ void wallet_rpc_server::handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code) {
+ try
+ {
+ std::rethrow_exception(e);
+ }
+ catch (const tools::error::daemon_busy& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY;
+ er.message = e.what();
+ }
+ catch (const tools::error::zero_destination& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_ZERO_DESTINATION;
+ er.message = e.what();
+ }
+ catch (const tools::error::not_enough_money& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY;
+ er.message = e.what();
+ }
+ catch (const tools::error::tx_not_possible& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_TX_NOT_POSSIBLE;
+ er.message = (boost::format(tr("Transaction not possible. Available only %s, transaction amount %s = %s + %s (fee)")) %
+ cryptonote::print_money(e.available()) %
+ cryptonote::print_money(e.tx_amount() + e.fee()) %
+ cryptonote::print_money(e.tx_amount()) %
+ cryptonote::print_money(e.fee())).str();
+ er.message = e.what();
+ }
+ catch (const tools::error::not_enough_outs_to_mix& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_ENOUGH_OUTS_TO_MIX;
+ er.message = e.what();
+ }
+ catch (const error::file_exists& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WALLET_ALREADY_EXISTS;
+ er.message = "Cannot create wallet. Already exists.";
+ }
+ catch (const error::invalid_password& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_INVALID_PASSWORD;
+ er.message = "Invalid password.";
+ }
+ catch (const std::exception& e)
+ {
+ er.code = default_error_code;
+ er.message = e.what();
+ }
+ catch (...)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR";
+ }
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
}
int main(int argc, char** argv) {
@@ -1892,11 +2028,19 @@ just_dir:
if (wal) wrpc.set_wallet(wal.release());
bool r = wrpc.init(&(vm.get()));
CHECK_AND_ASSERT_MES(r, 1, tools::wallet_rpc_server::tr("Failed to initialize wallet rpc server"));
- tools::signal_handler::install([&wrpc, &wal](int) {
+ tools::signal_handler::install([&wrpc](int) {
wrpc.send_stop_signal();
});
LOG_PRINT_L0(tools::wallet_rpc_server::tr("Starting wallet rpc server"));
- wrpc.run();
+ try
+ {
+ wrpc.run();
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR(tools::wallet_rpc_server::tr("Failed to run wallet: ") << e.what());
+ return 1;
+ }
LOG_PRINT_L0(tools::wallet_rpc_server::tr("Stopped wallet rpc server"));
try
{
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index dd54222b0..f2d98df6f 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -33,6 +33,7 @@
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <string>
+#include "common/util.h"
#include "net/http_server_impl_base.h"
#include "wallet_rpc_server_commands_defs.h"
#include "wallet2.h"
@@ -68,6 +69,11 @@ namespace tools
BEGIN_JSON_RPC_MAP("/json_rpc")
MAP_JON_RPC_WE("getbalance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE)
MAP_JON_RPC_WE("getaddress", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS)
+ MAP_JON_RPC_WE("create_address", on_create_address, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS)
+ MAP_JON_RPC_WE("label_address", on_label_address, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS)
+ MAP_JON_RPC_WE("get_accounts", on_get_accounts, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS)
+ MAP_JON_RPC_WE("create_account", on_create_account, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT)
+ MAP_JON_RPC_WE("label_account", on_label_account, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT)
MAP_JON_RPC_WE("getheight", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT)
MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER)
MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT)
@@ -107,8 +113,13 @@ namespace tools
//json_rpc
bool on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er);
bool on_getaddress(const wallet_rpc::COMMAND_RPC_GET_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS::response& res, epee::json_rpc::error& er);
+ bool on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er);
+ bool on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er);
+ bool on_get_accounts(const wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::response& res, epee::json_rpc::error& er);
+ bool on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er);
+ bool on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er);
bool on_getheight(const wallet_rpc::COMMAND_RPC_GET_HEIGHT::request& req, wallet_rpc::COMMAND_RPC_GET_HEIGHT::response& res, epee::json_rpc::error& er);
- bool validate_transfer(const std::list<wallet_rpc::transfer_destination> destinations, const std::string payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er);
+ bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er);
bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er);
bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er);
@@ -151,10 +162,11 @@ namespace tools
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd);
bool not_open(epee::json_rpc::error& er);
uint64_t adjust_mixin(uint64_t mixin);
+ void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code);
wallet2 *m_wallet;
std::string m_wallet_dir;
- std::string rpc_login_filename;
+ tools::private_file rpc_login_file;
std::atomic<bool> m_stop;
bool m_trusted_daemon;
epee::net_utils::http::http_simple_client m_http_client;
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index fa5c154de..f652fa7ff 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -31,6 +31,7 @@
#pragma once
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
#include "cryptonote_basic/cryptonote_basic.h"
+#include "cryptonote_basic/subaddress_index.h"
#include "crypto/hash.h"
#include "wallet_rpc_server_error_codes.h"
@@ -48,7 +49,28 @@ namespace wallet_rpc
{
struct request
{
+ uint32_t account_index;
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(account_index)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct per_subaddress_info
+ {
+ uint32_t address_index;
+ std::string address;
+ uint64_t balance;
+ uint64_t unlocked_balance;
+ std::string label;
+ uint64_t num_unspent_outputs;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address_index)
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(balance)
+ KV_SERIALIZE(unlocked_balance)
+ KV_SERIALIZE(label)
+ KV_SERIALIZE(num_unspent_outputs)
END_KV_SERIALIZE_MAP()
};
@@ -56,10 +78,12 @@ namespace wallet_rpc
{
uint64_t balance;
uint64_t unlocked_balance;
+ std::vector<per_subaddress_info> per_subaddress;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(balance)
KV_SERIALIZE(unlocked_balance)
+ KV_SERIALIZE(per_subaddress)
END_KV_SERIALIZE_MAP()
};
};
@@ -68,20 +92,164 @@ namespace wallet_rpc
{
struct request
{
+ uint32_t account_index;
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(account_index)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct address_info
+ {
+ std::string address;
+ std::string label;
+ uint32_t address_index;
+ bool used;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(label)
+ KV_SERIALIZE(address_index)
+ KV_SERIALIZE(used)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string address; // to remain compatible with older RPC format
+ std::vector<address_info> addresses;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(addresses)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_CREATE_ADDRESS
+ {
+ struct request
+ {
+ uint32_t account_index;
+ std::string label;
+
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(account_index)
+ KV_SERIALIZE(label)
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string address;
+ uint32_t address_index;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(address_index)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_LABEL_ADDRESS
+ {
+ struct request
+ {
+ cryptonote::subaddress_index index;
+ std::string label;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(index)
+ KV_SERIALIZE(label)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_GET_ACCOUNTS
+ {
+ struct request
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct subaddress_account_info
+ {
+ uint32_t account_index;
+ std::string base_address;
+ uint64_t balance;
+ uint64_t unlocked_balance;
+ std::string label;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(account_index)
+ KV_SERIALIZE(base_address)
+ KV_SERIALIZE(balance)
+ KV_SERIALIZE(unlocked_balance)
+ KV_SERIALIZE(label)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ uint64_t total_balance;
+ uint64_t total_unlocked_balance;
+ std::vector<subaddress_account_info> subaddress_accounts;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(total_balance)
+ KV_SERIALIZE(total_unlocked_balance)
+ KV_SERIALIZE(subaddress_accounts)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+ struct COMMAND_RPC_CREATE_ACCOUNT
+ {
+ struct request
+ {
+ std::string label;
BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(label)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ uint32_t account_index;
+ std::string address; // the 0-th address for convenience
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(account_index)
KV_SERIALIZE(address)
END_KV_SERIALIZE_MAP()
};
};
+ struct COMMAND_RPC_LABEL_ACCOUNT
+ {
+ struct request
+ {
+ uint32_t account_index;
+ std::string label;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(account_index)
+ KV_SERIALIZE(label)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
struct COMMAND_RPC_GET_HEIGHT
{
struct request
@@ -114,6 +282,8 @@ namespace wallet_rpc
struct request
{
std::list<transfer_destination> destinations;
+ uint32_t account_index;
+ std::set<uint32_t> subaddr_indices;
uint32_t priority;
uint64_t mixin;
uint64_t unlock_time;
@@ -124,6 +294,8 @@ namespace wallet_rpc
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(destinations)
+ KV_SERIALIZE(account_index)
+ KV_SERIALIZE(subaddr_indices)
KV_SERIALIZE(priority)
KV_SERIALIZE(mixin)
KV_SERIALIZE(unlock_time)
@@ -157,6 +329,8 @@ namespace wallet_rpc
struct request
{
std::list<transfer_destination> destinations;
+ uint32_t account_index;
+ std::set<uint32_t> subaddr_indices;
uint32_t priority;
uint64_t mixin;
uint64_t unlock_time;
@@ -167,6 +341,8 @@ namespace wallet_rpc
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(destinations)
+ KV_SERIALIZE(account_index)
+ KV_SERIALIZE(subaddr_indices)
KV_SERIALIZE(priority)
KV_SERIALIZE(mixin)
KV_SERIALIZE(unlock_time)
@@ -249,6 +425,8 @@ namespace wallet_rpc
struct request
{
std::string address;
+ uint32_t account_index;
+ std::set<uint32_t> subaddr_indices;
uint32_t priority;
uint64_t mixin;
uint64_t unlock_time;
@@ -260,6 +438,8 @@ namespace wallet_rpc
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(address)
+ KV_SERIALIZE(account_index)
+ KV_SERIALIZE(subaddr_indices)
KV_SERIALIZE(priority)
KV_SERIALIZE(mixin)
KV_SERIALIZE(unlock_time)
@@ -318,6 +498,7 @@ namespace wallet_rpc
uint64_t amount;
uint64_t block_height;
uint64_t unlock_time;
+ cryptonote::subaddress_index subaddr_index;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(payment_id)
@@ -325,6 +506,7 @@ namespace wallet_rpc
KV_SERIALIZE(amount)
KV_SERIALIZE(block_height)
KV_SERIALIZE(unlock_time)
+ KV_SERIALIZE(subaddr_index)
END_KV_SERIALIZE_MAP()
};
@@ -379,6 +561,7 @@ namespace wallet_rpc
uint64_t global_index;
std::string tx_hash;
uint64_t tx_size;
+ uint32_t subaddr_index;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(amount)
@@ -386,6 +569,7 @@ namespace wallet_rpc
KV_SERIALIZE(global_index)
KV_SERIALIZE(tx_hash)
KV_SERIALIZE(tx_size)
+ KV_SERIALIZE(subaddr_index)
END_KV_SERIALIZE_MAP()
};
@@ -394,9 +578,13 @@ namespace wallet_rpc
struct request
{
std::string transfer_type;
+ uint32_t account_index;
+ std::set<uint32_t> subaddr_indices;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(transfer_type)
+ KV_SERIALIZE(account_index)
+ KV_SERIALIZE(subaddr_indices)
END_KV_SERIALIZE_MAP()
};
@@ -470,10 +658,12 @@ namespace wallet_rpc
{
std::string standard_address;
std::string payment_id;
+ bool is_subaddress;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(standard_address)
KV_SERIALIZE(payment_id)
+ KV_SERIALIZE(is_subaddress)
END_KV_SERIALIZE_MAP()
};
};
@@ -561,6 +751,7 @@ namespace wallet_rpc
std::list<transfer_destination> destinations;
std::string type;
uint64_t unlock_time;
+ cryptonote::subaddress_index subaddr_index;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(txid);
@@ -573,6 +764,7 @@ namespace wallet_rpc
KV_SERIALIZE(destinations);
KV_SERIALIZE(type);
KV_SERIALIZE(unlock_time)
+ KV_SERIALIZE(subaddr_index);
END_KV_SERIALIZE_MAP()
};
@@ -589,6 +781,8 @@ namespace wallet_rpc
bool filter_by_height;
uint64_t min_height;
uint64_t max_height;
+ uint32_t account_index;
+ std::set<uint32_t> subaddr_indices;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(in);
@@ -599,6 +793,8 @@ namespace wallet_rpc
KV_SERIALIZE(filter_by_height);
KV_SERIALIZE(min_height);
KV_SERIALIZE(max_height);
+ KV_SERIALIZE(account_index);
+ KV_SERIALIZE(subaddr_indices);
END_KV_SERIALIZE_MAP()
};
diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h
index 3c79c0ac3..e74e9110b 100644
--- a/src/wallet/wallet_rpc_server_error_codes.h
+++ b/src/wallet/wallet_rpc_server_error_codes.h
@@ -44,3 +44,13 @@
#define WALLET_RPC_ERROR_CODE_WRONG_URI -11
#define WALLET_RPC_ERROR_CODE_WRONG_INDEX -12
#define WALLET_RPC_ERROR_CODE_NOT_OPEN -13
+#define WALLET_RPC_ERROR_CODE_ACCOUNT_INDEX_OUTOFBOUND -14
+#define WALLET_RPC_ERROR_CODE_ADDRESS_INDEX_OUTOFBOUND -15
+#define WALLET_RPC_ERROR_CODE_TX_NOT_POSSIBLE -16
+#define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_MONEY -17
+#define WALLET_RPC_ERROR_CODE_TX_TOO_LARGE -18
+#define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_OUTS_TO_MIX -19
+#define WALLET_RPC_ERROR_CODE_ZERO_DESTINATION -20
+#define WALLET_RPC_ERROR_CODE_WALLET_ALREADY_EXISTS -21
+#define WALLET_RPC_ERROR_CODE_INVALID_PASSWORD -22
+#define WALLET_RPC_ERROR_CODE_NO_WALLET_DIR -23