aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp41
-rw-r--r--src/blockchain_utilities/CMakeLists.txt4
-rw-r--r--src/blockchain_utilities/blockchain_blackball.cpp65
-rw-r--r--src/blocks/CMakeLists.txt12
-rw-r--r--src/blocks/checkpoints.datbin198276 -> 208420 bytes
-rw-r--r--src/checkpoints/checkpoints.cpp3
-rw-r--r--src/common/CMakeLists.txt8
-rw-r--r--src/common/dns_utils.cpp7
-rw-r--r--src/common/error.cpp75
-rw-r--r--src/common/error.h52
-rw-r--r--src/common/expect.cpp70
-rw-r--r--src/common/expect.h449
-rw-r--r--src/common/notify.cpp62
-rw-r--r--src/common/notify.h49
-rw-r--r--src/common/password.cpp51
-rw-r--r--src/common/password.h1
-rw-r--r--src/common/spawn.cpp144
-rw-r--r--src/common/spawn.h36
-rw-r--r--src/common/threadpool.cpp13
-rw-r--r--src/common/util.cpp50
-rw-r--r--src/common/util.h5
-rw-r--r--src/crypto/keccak.c11
-rw-r--r--src/crypto/slow-hash.c63
-rw-r--r--src/cryptonote_basic/cryptonote_basic.h1
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp95
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.h3
-rw-r--r--src/cryptonote_basic/miner.cpp6
-rw-r--r--src/cryptonote_core/blockchain.cpp29
-rw-r--r--src/cryptonote_core/blockchain.h11
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp31
-rw-r--r--src/cryptonote_core/cryptonote_core.h12
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp8
-rw-r--r--src/cryptonote_core/tx_pool.cpp52
-rw-r--r--src/cryptonote_core/tx_pool.h13
-rw-r--r--src/daemon/CMakeLists.txt2
-rw-r--r--src/daemon/daemon.cpp14
-rw-r--r--src/daemon/rpc_command_executor.cpp20
-rw-r--r--src/daemonizer/windows_daemonizer.inl1
-rw-r--r--src/debug_utilities/object_sizes.cpp1
-rw-r--r--src/device/CMakeLists.txt19
-rw-r--r--src/device/device.cpp29
-rw-r--r--src/device/device.hpp14
-rw-r--r--src/device/device_default.hpp2
-rw-r--r--src/device/device_io.hpp56
-rw-r--r--src/device/device_io_hid.cpp316
-rw-r--r--src/device/device_io_hid.hpp110
-rw-r--r--src/device/device_ledger.cpp209
-rw-r--r--src/device/device_ledger.hpp42
-rw-r--r--src/device/log.cpp41
-rw-r--r--src/device/log.hpp7
-rw-r--r--src/gen_multisig/gen_multisig.cpp21
-rw-r--r--src/multisig/multisig.cpp44
-rw-r--r--src/multisig/multisig.h24
-rw-r--r--src/p2p/net_node.cpp1
-rw-r--r--src/p2p/net_node.inl6
-rw-r--r--src/ringct/bulletproofs.cc32
-rw-r--r--src/ringct/rctOps.cpp19
-rw-r--r--src/ringct/rctOps.h1
-rw-r--r--src/ringct/rctSigs.cpp12
-rw-r--r--src/rpc/core_rpc_server.cpp32
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h6
-rw-r--r--src/rpc/daemon_messages.h2
-rw-r--r--src/serialization/serialization.h23
-rw-r--r--src/simplewallet/simplewallet.cpp318
-rw-r--r--src/simplewallet/simplewallet.h2
-rw-r--r--src/version.cpp.in4
-rw-r--r--src/wallet/api/pending_transaction.cpp6
-rw-r--r--src/wallet/api/wallet.cpp88
-rw-r--r--src/wallet/api/wallet.h17
-rw-r--r--src/wallet/api/wallet2_api.h33
-rw-r--r--src/wallet/api/wallet_manager.cpp8
-rw-r--r--src/wallet/api/wallet_manager.h1
-rw-r--r--src/wallet/node_rpc_proxy.cpp10
-rw-r--r--src/wallet/ringdb.cpp19
-rw-r--r--src/wallet/wallet2.cpp707
-rw-r--r--src/wallet/wallet2.h38
-rw-r--r--src/wallet/wallet_rpc_server.cpp130
-rw-r--r--src/wallet/wallet_rpc_server.h3
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h37
79 files changed, 3300 insertions, 759 deletions
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 9e22e2e4b..bd91f308a 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -649,7 +649,7 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks, uin
{
MDB_txn *rtxn;
mdb_txn_cursors *rcurs;
- block_rtxn_start(&rtxn, &rcurs);
+ bool my_rtxn = block_rtxn_start(&rtxn, &rcurs);
for (uint64_t block_num = block_start; block_num <= block_stop; ++block_num)
{
// we have access to block weight, which will be greater or equal to block size,
@@ -661,7 +661,7 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks, uin
// some blocks were to be skipped for being outliers.
++num_blocks_used;
}
- block_rtxn_stop();
+ if (my_rtxn) block_rtxn_stop();
avg_block_size = total_block_size / num_blocks_used;
MDEBUG("average block size across recent " << num_blocks_used << " blocks: " << avg_block_size);
}
@@ -1145,7 +1145,10 @@ BlockchainLMDB::~BlockchainLMDB()
// batch transaction shouldn't be active at this point. If it is, consider it aborted.
if (m_batch_active)
- batch_abort();
+ {
+ try { batch_abort(); }
+ catch (...) { /* ignore */ }
+ }
if (m_open)
close();
}
@@ -3391,8 +3394,10 @@ bool BlockchainLMDB::get_output_distribution(uint64_t amount, uint64_t from_heig
break;
}
+ distribution[0] += base;
for (size_t n = 1; n < distribution.size(); ++n)
distribution[n] += distribution[n - 1];
+ base = 0;
TXN_POSTFIX_RDONLY();
@@ -3488,7 +3493,17 @@ void BlockchainLMDB::fixup()
BlockchainDB::fixup();
}
-#define RENAME_DB(name) \
+#define RENAME_DB(name) do { \
+ char n2[] = name; \
+ MDB_dbi tdbi; \
+ n2[sizeof(n2)-2]--; \
+ /* play some games to put (name) on a writable page */ \
+ result = mdb_dbi_open(txn, n2, MDB_CREATE, &tdbi); \
+ if (result) \
+ throw0(DB_ERROR(lmdb_error("Failed to create " + std::string(n2) + ": ", result).c_str())); \
+ result = mdb_drop(txn, tdbi, 1); \
+ if (result) \
+ throw0(DB_ERROR(lmdb_error("Failed to delete " + std::string(n2) + ": ", result).c_str())); \
k.mv_data = (void *)name; \
k.mv_size = sizeof(name)-1; \
result = mdb_cursor_open(txn, 1, &c_cur); \
@@ -3498,7 +3513,7 @@ void BlockchainLMDB::fixup()
if (result) \
throw0(DB_ERROR(lmdb_error("Failed to get DB record for " name ": ", result).c_str())); \
ptr = (char *)k.mv_data; \
- ptr[sizeof(name)-2]++
+ ptr[sizeof(name)-2]++; } while(0)
#define LOGIF(y) if (ELPP->vRegistry()->allowed(y, "global"))
@@ -3577,7 +3592,9 @@ void BlockchainLMDB::migrate_0_1()
throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_heights: ", result).c_str()));
if (!i) {
MDB_stat ms;
- mdb_stat(txn, m_block_heights, &ms);
+ result = mdb_stat(txn, m_block_heights, &ms);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to query block_heights table: ", result).c_str()));
i = ms.ms_entries;
}
}
@@ -3680,7 +3697,9 @@ void BlockchainLMDB::migrate_0_1()
throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_timestamps: ", result).c_str()));
if (!i) {
MDB_stat ms;
- mdb_stat(txn, m_block_info, &ms);
+ result = mdb_stat(txn, m_block_info, &ms);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to query block_info table: ", result).c_str()));
i = ms.ms_entries;
}
}
@@ -3800,7 +3819,9 @@ void BlockchainLMDB::migrate_0_1()
throw0(DB_ERROR(lmdb_error("Failed to open a cursor for spent_keys: ", result).c_str()));
if (!i) {
MDB_stat ms;
- mdb_stat(txn, m_hf_versions, &ms);
+ result = mdb_stat(txn, m_hf_versions, &ms);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to query hf_versions table: ", result).c_str()));
i = ms.ms_entries;
}
}
@@ -3955,7 +3976,9 @@ void BlockchainLMDB::migrate_0_1()
throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs: ", result).c_str()));
if (!i) {
MDB_stat ms;
- mdb_stat(txn, m_txs, &ms);
+ result = mdb_stat(txn, m_txs, &ms);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to query txs table: ", result).c_str()));
i = ms.ms_entries;
if (i) {
MDB_val_set(pk, "txblk");
diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt
index 37bca671f..24a750eb0 100644
--- a/src/blockchain_utilities/CMakeLists.txt
+++ b/src/blockchain_utilities/CMakeLists.txt
@@ -32,6 +32,8 @@ if(PER_BLOCK_CHECKPOINT)
add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} --target=x86_64-apple-darwin11 -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*)
elseif(APPLE AND NOT DEPENDS)
add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*)
+ elseif(LINUX_32)
+ add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat)
else()
add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat)
endif()
@@ -182,7 +184,7 @@ target_link_libraries(blockchain_blackball
set_property(TARGET blockchain_blackball
PROPERTY
- OUTPUT_NAME "monero-blockchain-blackball")
+ OUTPUT_NAME "monero-blockchain-mark-spent-outputs")
install(TARGETS blockchain_blackball DESTINATION bin)
diff --git a/src/blockchain_utilities/blockchain_blackball.cpp b/src/blockchain_utilities/blockchain_blackball.cpp
index 1c6e54d10..d2ce5cf76 100644
--- a/src/blockchain_utilities/blockchain_blackball.cpp
+++ b/src/blockchain_utilities/blockchain_blackball.cpp
@@ -226,7 +226,7 @@ static void init(std::string cache_filename)
bool tx_active = false;
int dbr;
- MINFO("Creating blackball cache in " << cache_filename);
+ MINFO("Creating spent output cache in " << cache_filename);
tools::create_directories_if_necessary(cache_filename);
@@ -401,7 +401,8 @@ static bool for_all_transactions(const std::string &filename, uint64_t &start_id
}
mdb_cursor_close(cur);
- mdb_txn_commit(txn);
+ dbr = mdb_txn_commit(txn);
+ if (dbr) throw std::runtime_error("Failed to commit db transaction: " + std::string(mdb_strerror(dbr)));
tx_active = false;
mdb_dbi_close(env, dbi);
mdb_env_close(env);
@@ -471,7 +472,8 @@ static uint64_t find_first_diverging_transaction(const std::string &first_filena
for (int i = 0; i < 2; ++i)
{
mdb_cursor_close(cur[i]);
- mdb_txn_commit(txn[i]);
+ dbr = mdb_txn_commit(txn[i]);
+ if (dbr) throw std::runtime_error("Failed to query transaction: " + std::string(mdb_strerror(dbr)));
tx_active[i] = false;
mdb_dbi_close(env[i], dbi[i]);
mdb_env_close(env[i]);
@@ -534,12 +536,15 @@ static uint64_t get_num_spent_outputs()
return count;
}
-static void add_spent_output(MDB_cursor *cur, const output_data &od)
+static bool add_spent_output(MDB_cursor *cur, const output_data &od)
{
MDB_val k = {sizeof(od.amount), (void*)&od.amount};
MDB_val v = {sizeof(od.offset), (void*)&od.offset};
- int dbr = mdb_cursor_put(cur, &k, &v, 0);
- CHECK_AND_ASSERT_THROW_MES(!dbr || dbr == MDB_KEYEXIST, "Failed to add spent output: " + std::string(mdb_strerror(dbr)));
+ int dbr = mdb_cursor_put(cur, &k, &v, MDB_NODUPDATA);
+ if (dbr == MDB_KEYEXIST)
+ return false;
+ CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to add spent output: " + std::string(mdb_strerror(dbr)));
+ return true;
}
static bool is_output_spent(MDB_cursor *cur, const output_data &od)
@@ -675,7 +680,7 @@ static uint64_t get_ring_subset_instances(MDB_txn *txn, uint64_t amount, const s
uint64_t extra = 0;
std::vector<uint64_t> subset;
subset.reserve(ring.size());
- for (uint64_t mask = 1; mask < (1u << ring.size()) - 1; ++mask)
+ for (uint64_t mask = 1; mask < (((uint64_t)1) << ring.size()) - 1; ++mask)
{
subset.resize(0);
for (size_t i = 0; i < ring.size(); ++i)
@@ -1014,7 +1019,7 @@ int main(int argc, char* argv[])
po::options_description desc_cmd_only("Command line options");
po::options_description desc_cmd_sett("Command line options and settings options");
const command_line::arg_descriptor<std::string> arg_blackball_db_dir = {
- "blackball-db-dir", "Specify blackball database directory",
+ "spent-output-db-dir", "Specify spent output database directory",
get_default_db_path(),
};
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
@@ -1071,7 +1076,7 @@ int main(int argc, char* argv[])
return 1;
}
- mlog_configure(mlog_get_default_log_path("monero-blockchain-blackball.log"), true);
+ mlog_configure(mlog_get_default_log_path("monero-blockchain-find-spent-outputs.log"), true);
if (!command_line::is_arg_defaulted(vm, arg_log_level))
mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str());
else
@@ -1109,10 +1114,10 @@ int main(int argc, char* argv[])
return 1;
}
- const std::string cache_dir = (output_file_path / "blackball-cache").string();
+ const std::string cache_dir = (output_file_path / "spent-outputs-cache").string();
init(cache_dir);
- LOG_PRINT_L0("Scanning for blackballable outputs...");
+ LOG_PRINT_L0("Scanning for spent outputs...");
size_t done = 0;
@@ -1151,8 +1156,8 @@ int main(int argc, char* argv[])
if (!is_output_spent(cur, output_data(output.first, output.second)))
{
blackballs.push_back(output);
- add_spent_output(cur, output_data(output.first, output.second));
- inc_stat(txn, output.first ? "pre-rct-extra" : "rct-ring-extra");
+ if (add_spent_output(cur, output_data(output.first, output.second)))
+ inc_stat(txn, output.first ? "pre-rct-extra" : "rct-ring-extra");
}
}
if (!blackballs.empty())
@@ -1210,12 +1215,12 @@ int main(int argc, char* argv[])
const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[0]);
if (opt_verbose)
{
- MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in a 1-ring");
+ MINFO("Marking output " << output.first << "/" << output.second << " as spent, due to being used in a 1-ring");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
}
blackballs.push_back(output);
- add_spent_output(cur, output_data(txin.amount, absolute[0]));
- inc_stat(txn, txin.amount ? "pre-rct-ring-size-1" : "rct-ring-size-1");
+ if (add_spent_output(cur, output_data(txin.amount, absolute[0])))
+ inc_stat(txn, txin.amount ? "pre-rct-ring-size-1" : "rct-ring-size-1");
}
else if (n == 0 && instances == new_ring.size())
{
@@ -1224,12 +1229,12 @@ int main(int argc, char* argv[])
const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[o]);
if (opt_verbose)
{
- MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in " << new_ring.size() << " identical " << new_ring.size() << "-rings");
+ MINFO("Marking output " << output.first << "/" << output.second << " as spent, due to being used in " << new_ring.size() << " identical " << new_ring.size() << "-rings");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
}
blackballs.push_back(output);
- add_spent_output(cur, output_data(txin.amount, absolute[o]));
- inc_stat(txn, txin.amount ? "pre-rct-duplicate-rings" : "rct-duplicate-rings");
+ if (add_spent_output(cur, output_data(txin.amount, absolute[o])))
+ inc_stat(txn, txin.amount ? "pre-rct-duplicate-rings" : "rct-duplicate-rings");
}
}
else if (n == 0 && opt_check_subsets && get_ring_subset_instances(txn, txin.amount, new_ring) >= new_ring.size())
@@ -1239,12 +1244,12 @@ int main(int argc, char* argv[])
const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, absolute[o]);
if (opt_verbose)
{
- MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in " << new_ring.size() << " subsets of " << new_ring.size() << "-rings");
+ MINFO("Marking output " << output.first << "/" << output.second << " as spent, due to being used in " << new_ring.size() << " subsets of " << new_ring.size() << "-rings");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
}
blackballs.push_back(output);
- add_spent_output(cur, output_data(txin.amount, absolute[o]));
- inc_stat(txn, txin.amount ? "pre-rct-subset-rings" : "rct-subset-rings");
+ if (add_spent_output(cur, output_data(txin.amount, absolute[o])))
+ inc_stat(txn, txin.amount ? "pre-rct-subset-rings" : "rct-subset-rings");
}
}
else if (n > 0 && get_relative_ring(txn, txin.k_image, relative_ring))
@@ -1275,12 +1280,12 @@ int main(int argc, char* argv[])
const std::pair<uint64_t, uint64_t> output = std::make_pair(txin.amount, common[0]);
if (opt_verbose)
{
- MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in rings with a single common element");
+ MINFO("Marking output " << output.first << "/" << output.second << " as spent, due to being used in rings with a single common element");
std::cout << "\r" << start_idx << "/" << n_txes << " \r" << std::flush;
}
blackballs.push_back(output);
- add_spent_output(cur, output_data(txin.amount, common[0]));
- inc_stat(txn, txin.amount ? "pre-rct-key-image-attack" : "rct-key-image-attack");
+ if (add_spent_output(cur, output_data(txin.amount, common[0])))
+ inc_stat(txn, txin.amount ? "pre-rct-key-image-attack" : "rct-key-image-attack");
}
else
{
@@ -1387,13 +1392,13 @@ int main(int argc, char* argv[])
const std::pair<uint64_t, uint64_t> output = std::make_pair(od.amount, last_unknown);
if (opt_verbose)
{
- MINFO("Blackballing output " << output.first << "/" << output.second << ", due to being used in a " <<
+ MINFO("Marking output " << output.first << "/" << output.second << " as spent, due to being used in a " <<
absolute.size() << "-ring where all other outputs are known to be spent");
}
blackballs.push_back(output);
- add_spent_output(cur, output_data(od.amount, last_unknown));
+ if (add_spent_output(cur, output_data(od.amount, last_unknown)))
+ inc_stat(txn, od.amount ? "pre-rct-chain-reaction" : "rct-chain-reaction");
work_spent.push_back(output_data(od.amount, last_unknown));
- inc_stat(txn, od.amount ? "pre-rct-chain-reaction" : "rct-chain-reaction");
}
}
@@ -1415,7 +1420,7 @@ int main(int argc, char* argv[])
skip_secondary_passes:
uint64_t diff = get_num_spent_outputs() - start_blackballed_outputs;
- LOG_PRINT_L0(std::to_string(diff) << " new outputs blackballed, " << get_num_spent_outputs() << " total outputs blackballed");
+ LOG_PRINT_L0(std::to_string(diff) << " new outputs marked as spent, " << get_num_spent_outputs() << " total outputs marked as spent");
MDB_txn *txn;
dbr = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
@@ -1455,7 +1460,7 @@ skip_secondary_passes:
mdb_txn_abort(txn);
}
- LOG_PRINT_L0("Blockchain blackball data exported OK");
+ LOG_PRINT_L0("Blockchain spent output data exported OK");
close_db(env0, txn0, cur0, dbi0);
close();
return 0;
diff --git a/src/blocks/CMakeLists.txt b/src/blocks/CMakeLists.txt
index cf3f0b354..ebb5408cc 100644
--- a/src/blocks/CMakeLists.txt
+++ b/src/blocks/CMakeLists.txt
@@ -30,9 +30,15 @@ if(APPLE)
add_library(blocks STATIC blockexports.c)
set_target_properties(blocks PROPERTIES LINKER_LANGUAGE C)
else()
- add_custom_command(OUTPUT blocks.o MAIN_DEPENDENCY blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat)
- add_custom_command(OUTPUT testnet_blocks.o MAIN_DEPENDENCY testnet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat)
- add_custom_command(OUTPUT stagenet_blocks.o MAIN_DEPENDENCY stagenet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/stagenet_blocks.o stagenet_blocks.dat)
+ if(LINUX_32)
+ add_custom_command(OUTPUT blocks.o MAIN_DEPENDENCY blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat)
+ add_custom_command(OUTPUT testnet_blocks.o MAIN_DEPENDENCY testnet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat)
+ add_custom_command(OUTPUT stagenet_blocks.o MAIN_DEPENDENCY stagenet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/stagenet_blocks.o stagenet_blocks.dat)
+ else()
+ add_custom_command(OUTPUT blocks.o MAIN_DEPENDENCY blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat)
+ add_custom_command(OUTPUT testnet_blocks.o MAIN_DEPENDENCY testnet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat)
+ add_custom_command(OUTPUT stagenet_blocks.o MAIN_DEPENDENCY stagenet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/stagenet_blocks.o stagenet_blocks.dat)
+ endif()
add_library(blocks STATIC blocks.o testnet_blocks.o stagenet_blocks.o blockexports.c)
set_target_properties(blocks PROPERTIES LINKER_LANGUAGE C)
endif()
diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat
index 085558504..03e2cd2a8 100644
--- a/src/blocks/checkpoints.dat
+++ b/src/blocks/checkpoints.dat
Binary files differ
diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp
index ef1ee171d..6251fcc91 100644
--- a/src/checkpoints/checkpoints.cpp
+++ b/src/checkpoints/checkpoints.cpp
@@ -165,6 +165,7 @@ namespace cryptonote
{
ADD_CHECKPOINT(0, "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b");
ADD_CHECKPOINT(1000000, "46b690b710a07ea051bc4a6b6842ac37be691089c0f7758cfeec4d5fc0b4a258");
+ ADD_CHECKPOINT(1058600, "12904f6b4d9e60fd875674e07147d2c83d6716253f046af7b894c3e81da7e1bd");
return true;
}
if (nettype == STAGENET)
@@ -208,7 +209,7 @@ namespace cryptonote
ADD_CHECKPOINT(1450000, "ac94e8860093bc7c83e4e91215cba1d663421ecf4067a0ae609c3a8b52bcfac2");
ADD_CHECKPOINT(1530000, "01759bce497ec38e63c78b1038892169203bb78f87e488172f6b854fcd63ba7e");
ADD_CHECKPOINT(1579000, "7d0d7a2346373afd41ed1e744a939fc5d474a7dbaa257be5c6fff4009e789241");
-
+ ADD_CHECKPOINT(1668900, "ac2dcaf3d2f58ffcf8391639f0f1ebafcb8eac43c49479c7c37f611868d07568");
return true;
}
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index c6bac2199..aed9bfee7 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -33,10 +33,14 @@ set(common_sources
command_line.cpp
dns_utils.cpp
download.cpp
+ error.cpp
+ expect.cpp
util.cpp
i18n.cpp
+ notify.cpp
password.cpp
perf_timer.cpp
+ spawn.cpp
threadpool.cpp
updates.cpp
aligned.c)
@@ -55,8 +59,11 @@ set(common_private_headers
common_fwd.h
dns_utils.h
download.h
+ error.h
+ expect.h
http_connection.h
int-util.h
+ notify.h
pod-class.h
rpc_client.h
scoped_message_writer.h
@@ -66,6 +73,7 @@ set(common_private_headers
i18n.h
password.h
perf_timer.h
+ spawn.h
stack_trace.h
threadpool.h
updates.h
diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp
index 3f2bde620..f2b270981 100644
--- a/src/common/dns_utils.cpp
+++ b/src/common/dns_utils.cpp
@@ -46,10 +46,11 @@ namespace bf = boost::filesystem;
static const char *DEFAULT_DNS_PUBLIC_ADDR[] =
{
"194.150.168.168", // CCC (Germany)
- "81.3.27.54", // Lightning Wire Labs (Germany)
- "31.3.135.232", // OpenNIC (Switzerland)
"80.67.169.40", // FDN (France)
- "209.58.179.186", // Cyberghost (Singapore)
+ "89.233.43.71", // http://censurfridns.dk (Denmark)
+ "109.69.8.51", // punCAT (Spain)
+ "77.109.148.137", // Xiala.net (Switzerland)
+ "193.58.251.251", // SkyDNS (Russia)
};
static boost::mutex instance_lock;
diff --git a/src/common/error.cpp b/src/common/error.cpp
new file mode 100644
index 000000000..e091e4478
--- /dev/null
+++ b/src/common/error.cpp
@@ -0,0 +1,75 @@
+// Copyright (c) 2018, 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 "error.h"
+
+#include <string>
+
+namespace
+{
+ struct category final : std::error_category
+ {
+ virtual const char* name() const noexcept override final
+ {
+ return "common_category()";
+ }
+
+ virtual std::string message(int value) const override final
+ {
+ switch (common_error(value))
+ {
+ case common_error::kInvalidArgument:
+ return make_error_code(std::errc::invalid_argument).message();
+ case common_error::kInvalidErrorCode:
+ return "expect<T> was given an error value of zero";
+ default:
+ break;
+ }
+ return "Unknown basic_category() value";
+ }
+
+ virtual std::error_condition default_error_condition(int value) const noexcept override final
+ {
+ // maps specific errors to generic `std::errc` cases.
+ switch (common_error(value))
+ {
+ case common_error::kInvalidArgument:
+ case common_error::kInvalidErrorCode:
+ return std::errc::invalid_argument;
+ default:
+ break;
+ }
+ return std::error_condition{value, *this};
+ }
+ };
+}
+
+std::error_category const& common_category() noexcept
+{
+ static const category instance{};
+ return instance;
+}
+
diff --git a/src/common/error.h b/src/common/error.h
new file mode 100644
index 000000000..6fef3eb4b
--- /dev/null
+++ b/src/common/error.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2018, 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 <system_error>
+#include <type_traits>
+
+enum class common_error : int
+{
+ // 0 is reserved for no error, as per expect<T>
+ kInvalidArgument = 1, //!< A function argument is invalid
+ kInvalidErrorCode //!< Default `std::error_code` given to `expect<T>`
+};
+
+std::error_category const& common_category() noexcept;
+
+inline std::error_code make_error_code(::common_error value) noexcept
+{
+ return std::error_code{int(value), common_category()};
+}
+
+namespace std
+{
+ template<>
+ struct is_error_code_enum<::common_error>
+ : true_type
+ {};
+}
diff --git a/src/common/expect.cpp b/src/common/expect.cpp
new file mode 100644
index 000000000..c86e23e95
--- /dev/null
+++ b/src/common/expect.cpp
@@ -0,0 +1,70 @@
+//
+// 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 "expect.h"
+
+#include <easylogging++.h>
+#include <string>
+
+namespace detail
+{
+ namespace
+ {
+ std::string generate_error(const char* msg, const char* file, unsigned line)
+ {
+ std::string error_msg{};
+ if (msg)
+ {
+ error_msg.append(msg);
+ if (file)
+ error_msg.append(" (");
+ }
+ if (file)
+ {
+ error_msg.append("thrown at ");
+
+ // remove path, get just filename + extension
+ char buff[256] = {0};
+ el::base::utils::File::buildBaseFilename(file, buff, sizeof(buff) - 1);
+ error_msg.append(buff);
+
+ error_msg.push_back(':');
+ error_msg.append(std::to_string(line));
+ }
+ if (msg && file)
+ error_msg.push_back(')');
+ return error_msg;
+ }
+ }
+
+ void expect::throw_(std::error_code ec, const char* msg, const char* file, unsigned line)
+ {
+ if (msg || file)
+ throw std::system_error{ec, generate_error(msg, file, line)};
+ throw std::system_error{ec};
+ }
+} // detail
diff --git a/src/common/expect.h b/src/common/expect.h
new file mode 100644
index 000000000..72e4060a7
--- /dev/null
+++ b/src/common/expect.h
@@ -0,0 +1,449 @@
+// Copyright (c) 2018, 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 <cassert>
+#include <system_error>
+#include <type_traits>
+#include <utility>
+
+#include "common/error.h"
+
+//! If precondition fails, return `::error::kInvalidArgument` in current scope.
+#define MONERO_PRECOND(...) \
+ do \
+ { \
+ if (!( __VA_ARGS__ )) \
+ return {::common_error::kInvalidArgument}; \
+ } while (0)
+
+//! Check `expect<void>` and return errors in current scope.
+#define MONERO_CHECK(...) \
+ do \
+ { \
+ const ::expect<void> result = __VA_ARGS__ ; \
+ if (!result) \
+ return result.error(); \
+ } while (0)
+
+/*! Get `T` from `expect<T>` by `std::move` as-if by function call.
+ `expect<void>` returns nothing.
+
+ \throw std::system_error with `expect<T>::error()`, filename and line
+ number when `expect<T>::has_error() == true`.*/
+#define MONERO_UNWRAP(...) \
+ ::detail::expect::unwrap( __VA_ARGS__ , nullptr, __FILE__ , __LINE__ )
+
+/* \throw std::system_error with `code` and `msg` as part of the details. The
+filename and line number will automatically be injected into the explanation
+string. `code` can be any enum convertible to `std::error_code`. */
+#define MONERO_THROW(code, msg) \
+ ::detail::expect::throw_( code , msg , __FILE__ , __LINE__ )
+
+
+template<typename> class expect;
+
+namespace detail
+{
+ // Shortens the characters in the places that `enable_if` is used below.
+ template<bool C>
+ using enable_if = typename std::enable_if<C>::type;
+
+ struct expect
+ {
+ //! \throw std::system_error with `ec`, optional `msg` and/or optional `file` + `line`.
+ static void throw_(std::error_code ec, const char* msg, const char* file, unsigned line);
+
+ //! If `result.has_error()` call `throw_`. Otherwise, \return `*result` by move.
+ template<typename T>
+ static T unwrap(::expect<T>&& result, const char* error_msg, const char* file, unsigned line)
+ {
+ if (!result)
+ throw_(result.error(), error_msg, file, line);
+ return std::move(*result);
+ }
+
+ //! If `result.has_error()` call `throw_`.
+ static void unwrap(::expect<void>&& result, const char* error_msg, const char* file, unsigned line);
+ };
+}
+
+/*!
+ `expect<T>` is a value or error implementation, similar to Rust std::result
+ or various C++ proposals (boost::expected, boost::outcome). This
+ implementation currently has a strict error type, `std::error_code`, and a
+ templated value type `T`. `expect<T>` is implicitly convertible from `T`
+ or `std::error_code`, and one `expect<T>` object type is implicitly
+ convertible to another `expect<U>` object iff the destination value type
+ can be implicitly constructed from the source value type (i.e.
+ `struct U { ... U(T src) { ...} ... };`).
+
+ `operator==` and `operator!=` are the only comparison operators provided;
+ comparison between different value types is allowed provided the two values
+ types have a `operator==` defined between them (i.e.
+ `assert(expect<int>{100} == expect<short>{100});`). Comparisons can also be
+ done against `std::error_code` objects or error code enums directly (i.e.
+ `assert(expect<int>{make_error_code(common_error::kInvalidArgument)} == error::kInvalidArgument)`).
+ Comparison of default constructed `std::error_code` will always fail.
+ "Generic" comparisons can be done with `std::error_condition` via the `matches`
+ method only (i.e.
+ `assert(expect<int>{make_error_code{common_error::kInvalidErrorCode}.matches(std::errc::invalid_argument))`),
+ `operator==` and `operator!=` will not work with `std::errc` or
+ `std::error_condition`. A comparison with `matches` is more expensive
+ because an equivalency between error categories is computed, but is
+ recommended when an error can be one of several categories (this is going
+ to be the case in nearly every situation when calling a function from
+ another C++ struct/class).
+
+ `expect<void>` is a special case with no stored value. It is used by
+ functions that can fail, but otherwise would return `void`. It is useful
+ for consistency; all macros, standalone functions, and comparison operators
+ work with `expect<void>`.
+
+ \note See `src/common/error.h` for creating a custom error enum.
+ */
+template<typename T>
+class expect
+{
+ static_assert(std::is_nothrow_destructible<T>(), "T must have a nothrow destructor");
+
+ template<typename U>
+ static constexpr bool is_convertible() noexcept
+ {
+ return std::is_constructible<T, U>() &&
+ std::is_convertible<U, T>();
+ }
+
+ // MEMBERS
+ std::error_code code_;
+ typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_;
+ // MEMBERS
+
+ T& get() noexcept
+ {
+ assert(has_value());
+ return *reinterpret_cast<T*>(std::addressof(storage_));
+ }
+
+ T const& get() const noexcept
+ {
+ assert(has_value());
+ return *reinterpret_cast<T const*>(std::addressof(storage_));
+ }
+
+ template<typename U>
+ void store(U&& value) noexcept(std::is_nothrow_constructible<T, U>())
+ {
+ new (std::addressof(storage_)) T{std::forward<U>(value)};
+ code_ = std::error_code{};
+ }
+
+ void maybe_throw() const
+ {
+ if (has_error())
+ ::detail::expect::throw_(error(), nullptr, nullptr, 0);
+ }
+
+public:
+ using value_type = T;
+ using error_type = std::error_code;
+
+ expect() = delete;
+
+ /*! Store an error, `code`, in the `expect` object. If `code` creates a
+ `std::error_code` object whose `.value() == 0`, then `error()` will be set
+ to `::common_error::kInvalidErrorCode`. */
+ expect(std::error_code const& code) noexcept
+ : code_(code), storage_()
+ {
+ if (!has_error())
+ code_ = ::common_error::kInvalidErrorCode;
+ }
+
+ //! Store a value, `val`, in the `expect` object.
+ expect(T val) noexcept(std::is_nothrow_move_constructible<T>())
+ : code_(), storage_()
+ {
+ store(std::move(val));
+ }
+
+ expect(expect const& src) noexcept(std::is_nothrow_copy_constructible<T>())
+ : code_(src.error()), storage_()
+ {
+ if (src.has_value())
+ store(src.get());
+ }
+
+ //! Copy conversion from `U` to `T`.
+ template<typename U, typename = detail::enable_if<is_convertible<U const&>()>>
+ expect(expect<U> const& src) noexcept(std::is_nothrow_constructible<T, U const&>())
+ : code_(src.error()), storage_()
+ {
+ if (src.has_value())
+ store(*src);
+ }
+
+ expect(expect&& src) noexcept(std::is_nothrow_move_constructible<T>())
+ : code_(src.error()), storage_()
+ {
+ if (src.has_value())
+ store(std::move(src.get()));
+ }
+
+ //! Move conversion from `U` to `T`.
+ template<typename U, typename = detail::enable_if<is_convertible<U>()>>
+ expect(expect<U>&& src) noexcept(std::is_nothrow_constructible<T, U>())
+ : code_(src.error()), storage_()
+ {
+ if (src.has_value())
+ store(std::move(*src));
+ }
+
+ ~expect() noexcept
+ {
+ if (has_value())
+ get().~T();
+ }
+
+ expect& operator=(expect const& src) noexcept(std::is_nothrow_copy_constructible<T>() && std::is_nothrow_copy_assignable<T>())
+ {
+ if (this != std::addressof(src))
+ {
+ if (has_value() && src.has_value())
+ get() = src.get();
+ else if (has_value())
+ get().~T();
+ else if (src.has_value())
+ store(src.get());
+ code_ = src.error();
+ }
+ return *this;
+ }
+
+ /*! Move `src` into `this`. If `src.has_value() && addressof(src) != this`
+ then `src.value() will be in a "moved from state". */
+ expect& operator=(expect&& src) noexcept(std::is_nothrow_move_constructible<T>() && std::is_nothrow_move_assignable<T>())
+ {
+ if (this != std::addressof(src))
+ {
+ if (has_value() && src.has_value())
+ get() = std::move(src.get());
+ else if (has_value())
+ get().~T();
+ else if (src.has_value())
+ store(std::move(src.get()));
+ code_ = src.error();
+ }
+ return *this;
+ }
+
+ //! \return True if `this` is storing a value instead of an error.
+ explicit operator bool() const noexcept { return has_value(); }
+
+ //! \return True if `this` is storing an error instead of a value.
+ bool has_error() const noexcept { return bool(code_); }
+
+ //! \return True if `this` is storing a value instead of an error.
+ bool has_value() const noexcept { return !has_error(); }
+
+ //! \return Error - always safe to call. Empty when `!has_error()`.
+ std::error_code error() const noexcept { return code_; }
+
+ //! \return Value if `has_value()` otherwise \throw `std::system_error{error()}`.
+ T& value() &
+ {
+ maybe_throw();
+ return get();
+ }
+
+ //! \return Value if `has_value()` otherwise \throw `std::system_error{error()}`.
+ T const& value() const &
+ {
+ maybe_throw();
+ return get();
+ }
+
+ /*! Same as other overloads, but expressions such as `foo(bar().value())`
+ will automatically perform moves with no copies. */
+ T&& value() &&
+ {
+ maybe_throw();
+ return std::move(get());
+ }
+
+ //! \return Value, \pre `has_value()`.
+ T* operator->() noexcept { return std::addressof(get()); }
+ //! \return Value, \pre `has_value()`.
+ T const* operator->() const noexcept { return std::addressof(get()); }
+ //! \return Value, \pre `has_value()`.
+ T& operator*() noexcept { return get(); }
+ //! \return Value, \pre `has_value()`.
+ T const& operator*() const noexcept { return get(); }
+
+ /*!
+ \note This function is `noexcept` when `U == T` is `noexcept`.
+ \return True if `has_value() == rhs.has_value()` and if values or errors are equal.
+ */
+ template<typename U>
+ bool equal(expect<U> const& rhs) const noexcept(noexcept(*std::declval<expect<T>>() == *rhs))
+ {
+ return has_value() && rhs.has_value() ?
+ get() == *rhs : error() == rhs.error();
+ }
+
+ //! \return False if `has_value()`, otherwise `error() == rhs`.
+ bool equal(std::error_code const& rhs) const noexcept
+ {
+ return has_error() && error() == rhs;
+ }
+
+ /*!
+ \note This function is `noexcept` when `U == T` is `noexcept`.
+ \return False if `has_error()`, otherwise `value() == rhs`.
+ */
+ template<typename U, typename = detail::enable_if<!std::is_constructible<std::error_code, U>::value>>
+ bool equal(U const& rhs) const noexcept(noexcept(*std::declval<expect<T>>() == rhs))
+ {
+ return has_value() && get() == rhs;
+ }
+
+ //! \return False if `has_value()`, otherwise `error() == rhs`.
+ bool matches(std::error_condition const& rhs) const noexcept
+ {
+ return has_error() && error() == rhs;
+ }
+};
+
+template<>
+class expect<void>
+{
+ std::error_code code_;
+
+public:
+ using value_type = void;
+ using error_type = std::error_code;
+
+ //! Create a successful object.
+ expect() noexcept
+ : code_()
+ {}
+
+ expect(std::error_code const& code) noexcept
+ : code_(code)
+ {
+ if (!has_error())
+ code_ = ::common_error::kInvalidErrorCode;
+ }
+
+ expect(expect const&) = default;
+ ~expect() = default;
+ expect& operator=(expect const&) = default;
+
+ //! \return True if `this` is storing a value instead of an error.
+ explicit operator bool() const noexcept { return !has_error(); }
+
+ //! \return True if `this` is storing an error instead of a value.
+ bool has_error() const noexcept { return bool(code_); }
+
+ //! \return Error - alway
+ std::error_code error() const noexcept { return code_; }
+
+ //! \return `error() == rhs.error()`.
+ bool equal(expect const& rhs) const noexcept
+ {
+ return error() == rhs.error();
+ }
+
+ //! \return `has_error() && error() == rhs`.
+ bool equal(std::error_code const& rhs) const noexcept
+ {
+ return has_error() && error() == rhs;
+ }
+
+ //! \return False if `has_value()`, otherwise `error() == rhs`.
+ bool matches(std::error_condition const& rhs) const noexcept
+ {
+ return has_error() && error() == rhs;
+ }
+};
+
+//! \return An `expect<void>` object with `!has_error()`.
+inline expect<void> success() noexcept { return expect<void>{}; }
+
+template<typename T, typename U>
+inline
+bool operator==(expect<T> const& lhs, expect<U> const& rhs) noexcept(noexcept(lhs.equal(rhs)))
+{
+ return lhs.equal(rhs);
+}
+
+template<typename T, typename U>
+inline
+bool operator==(expect<T> const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs)))
+{
+ return lhs.equal(rhs);
+}
+
+template<typename T, typename U>
+inline
+bool operator==(T const& lhs, expect<U> const& rhs) noexcept(noexcept(rhs.equal(lhs)))
+{
+ return rhs.equal(lhs);
+}
+
+template<typename T, typename U>
+inline
+bool operator!=(expect<T> const& lhs, expect<U> const& rhs) noexcept(noexcept(lhs.equal(rhs)))
+{
+ return !lhs.equal(rhs);
+}
+
+template<typename T, typename U>
+inline
+bool operator!=(expect<T> const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs)))
+{
+ return !lhs.equal(rhs);
+}
+
+template<typename T, typename U>
+inline
+bool operator!=(T const& lhs, expect<U> const& rhs) noexcept(noexcept(rhs.equal(lhs)))
+{
+ return !rhs.equal(lhs);
+}
+
+namespace detail
+{
+ inline void expect::unwrap(::expect<void>&& result, const char* error_msg, const char* file, unsigned line)
+ {
+ if (!result)
+ throw_(result.error(), error_msg, file, line);
+ }
+}
+
diff --git a/src/common/notify.cpp b/src/common/notify.cpp
new file mode 100644
index 000000000..cadc68ea7
--- /dev/null
+++ b/src/common/notify.cpp
@@ -0,0 +1,62 @@
+// Copyright (c) 2018, 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 <boost/algorithm/string.hpp>
+#include "misc_log_ex.h"
+#include "file_io_utils.h"
+#include "spawn.h"
+#include "notify.h"
+
+namespace tools
+{
+
+/*
+ TODO:
+ - Improve tokenization to handle paths containing whitespaces, quotes, etc.
+ - Windows unicode support (implies implementing unicode command line parsing code)
+*/
+Notify::Notify(const char *spec)
+{
+ CHECK_AND_ASSERT_THROW_MES(spec, "Null spec");
+
+ boost::split(args, spec, boost::is_any_of(" "));
+ CHECK_AND_ASSERT_THROW_MES(args.size() > 0, "Failed to parse spec");
+ filename = args[0];
+ CHECK_AND_ASSERT_THROW_MES(epee::file_io_utils::is_file_exist(filename), "File not found: " << filename);
+}
+
+int Notify::notify(const char *parameter)
+{
+ std::vector<std::string> margs = args;
+ for (std::string &s: margs)
+ boost::replace_all(s, "%s", parameter);
+
+ return tools::spawn(filename.c_str(), margs, false);
+}
+
+}
diff --git a/src/common/notify.h b/src/common/notify.h
new file mode 100644
index 000000000..81aacebb0
--- /dev/null
+++ b/src/common/notify.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2018, 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 <string>
+#include <vector>
+
+namespace tools
+{
+
+class Notify
+{
+public:
+ Notify(const char *spec);
+
+ int notify(const char *parameter);
+
+private:
+ std::string filename;
+ std::vector<std::string> args;
+};
+
+}
diff --git a/src/common/password.cpp b/src/common/password.cpp
index 5671c4a4e..b3c51128f 100644
--- a/src/common/password.cpp
+++ b/src/common/password.cpp
@@ -56,43 +56,59 @@ namespace
bool read_from_tty(epee::wipeable_string& pass, bool hide_input)
{
- static constexpr const char BACKSPACE = 8;
-
HANDLE h_cin = ::GetStdHandle(STD_INPUT_HANDLE);
DWORD mode_old;
::GetConsoleMode(h_cin, &mode_old);
- DWORD mode_new = mode_old & ~((hide_input ? ENABLE_ECHO_INPUT : 0) | ENABLE_LINE_INPUT);
+ DWORD mode_new = mode_old & ~(hide_input ? ENABLE_ECHO_INPUT : 0);
::SetConsoleMode(h_cin, mode_new);
bool r = true;
pass.reserve(tools::password_container::max_password_size);
+ std::vector<int> chlen;
+ chlen.reserve(tools::password_container::max_password_size);
while (pass.size() < tools::password_container::max_password_size)
{
DWORD read;
- char ch;
- r = (TRUE == ::ReadConsoleA(h_cin, &ch, 1, &read, NULL));
+ wchar_t ucs2_ch;
+ r = (TRUE == ::ReadConsoleW(h_cin, &ucs2_ch, 1, &read, NULL));
r &= (1 == read);
+
if (!r)
{
break;
}
- else if (ch == '\n' || ch == '\r')
+ else if (ucs2_ch == L'\r')
+ {
+ continue;
+ }
+ else if (ucs2_ch == L'\n')
{
std::cout << std::endl;
break;
}
- else if (ch == BACKSPACE)
+ else if (ucs2_ch == L'\b')
{
if (!pass.empty())
{
- pass.pop_back();
+ int len = chlen.back();
+ chlen.pop_back();
+ while(len-- > 0)
+ pass.pop_back();
}
+ continue;
}
- else
- {
- pass.push_back(ch);
- }
+
+ char utf8_ch[8] = {0};
+ int len;
+ if((len = WideCharToMultiByte(CP_UTF8, 0, &ucs2_ch, 1, utf8_ch, sizeof(utf8_ch), NULL, NULL)) <= 0)
+ break;
+
+ if(pass.size() + len >= tools::password_container::max_password_size)
+ break;
+
+ chlen.push_back(len);
+ pass += utf8_ch;
}
::SetConsoleMode(h_cin, mode_old);
@@ -146,6 +162,13 @@ namespace
if (!aPass.empty())
{
aPass.pop_back();
+ if (!hide_input)
+ std::cout << "\b\b\b \b\b\b" << std::flush;
+ }
+ else
+ {
+ if (!hide_input)
+ std::cout << "\b\b \b\b" << std::flush;
}
}
else
@@ -221,6 +244,10 @@ namespace tools
: m_password(std::move(password))
{
}
+ password_container::password_container(const epee::wipeable_string& password) noexcept
+ : m_password(password)
+ {
+ }
password_container::~password_container() noexcept
{
diff --git a/src/common/password.h b/src/common/password.h
index 529881e40..beb98283b 100644
--- a/src/common/password.h
+++ b/src/common/password.h
@@ -47,6 +47,7 @@ namespace tools
//! `password` is used as password
password_container(std::string&& password) noexcept;
+ password_container(const epee::wipeable_string& password) noexcept;
//! \return A password from stdin TTY prompt or `std::cin` pipe.
static boost::optional<password_container> prompt(bool verify, const char *mesage = "Password", bool hide_input = true);
diff --git a/src/common/spawn.cpp b/src/common/spawn.cpp
new file mode 100644
index 000000000..0a2ce8387
--- /dev/null
+++ b/src/common/spawn.cpp
@@ -0,0 +1,144 @@
+// Copyright (c) 2018, 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 <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#ifdef _WIN32
+#include <boost/algorithm/string/join.hpp>
+#include <boost/scope_exit.hpp>
+#include <windows.h>
+#else
+#include <sys/wait.h>
+#endif
+
+#include "misc_log_ex.h"
+#include "util.h"
+#include "spawn.h"
+
+namespace tools
+{
+
+int spawn(const char *filename, const std::vector<std::string>& args, bool wait)
+{
+#ifdef _WIN32
+ std::string joined = boost::algorithm::join(args, " ");
+ char *commandLine = !joined.empty() ? &joined[0] : nullptr;
+ STARTUPINFOA si = {};
+ si.cb = sizeof(si);
+ PROCESS_INFORMATION pi;
+ if (!CreateProcessA(filename, commandLine, nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi))
+ {
+ MERROR("CreateProcess failed. Error code " << GetLastError());
+ return -1;
+ }
+
+ BOOST_SCOPE_EXIT(&pi)
+ {
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+ }
+ BOOST_SCOPE_EXIT_END
+
+ if (!wait)
+ {
+ return 0;
+ }
+
+ DWORD result = WaitForSingleObject(pi.hProcess, INFINITE);
+ if (result != WAIT_OBJECT_0)
+ {
+ MERROR("WaitForSingleObject failed. Result " << result << ", error code " << GetLastError());
+ return -1;
+ }
+
+ DWORD exitCode;
+ if (!GetExitCodeProcess(pi.hProcess, &exitCode))
+ {
+ MERROR("GetExitCodeProcess failed. Error code " << GetLastError());
+ return -1;
+ }
+
+ MINFO("Child exited with " << exitCode);
+ return static_cast<int>(exitCode);
+#else
+ char **argv = (char**)alloca(sizeof(char*) * (args.size() + 1));
+ for (size_t n = 0; n < args.size(); ++n)
+ argv[n] = (char*)args[n].c_str();
+ argv[args.size()] = NULL;
+
+ pid_t pid = fork();
+ if (pid < 0)
+ {
+ MERROR("Error forking: " << strerror(errno));
+ return -1;
+ }
+
+ // child
+ if (pid == 0)
+ {
+ tools::closefrom(3);
+ close(0);
+ char *envp[] = {NULL};
+ execve(filename, argv, envp);
+ MERROR("Failed to execve: " << strerror(errno));
+ return -1;
+ }
+
+ // parent
+ if (pid > 0)
+ {
+ if (!wait)
+ return 0;
+
+ while (1)
+ {
+ int wstatus = 0;
+ pid_t w = waitpid(pid, &wstatus, WUNTRACED | WCONTINUED);
+ if (w < 0) {
+ MERROR("Error waiting for child: " << strerror(errno));
+ return -1;
+ }
+ if (WIFEXITED(wstatus))
+ {
+ MINFO("Child exited with " << WEXITSTATUS(wstatus));
+ return WEXITSTATUS(wstatus);
+ }
+ if (WIFSIGNALED(wstatus))
+ {
+ MINFO("Child killed by " << WEXITSTATUS(wstatus));
+ return WEXITSTATUS(wstatus);
+ }
+ }
+ }
+ MERROR("Secret passage found");
+ return -1;
+#endif
+}
+
+}
diff --git a/src/common/spawn.h b/src/common/spawn.h
new file mode 100644
index 000000000..c90a0f790
--- /dev/null
+++ b/src/common/spawn.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, 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
+
+namespace tools
+{
+
+int spawn(const char *filename, const std::vector<std::string>& args, bool wait);
+
+}
diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp
index 6b69e2a12..37825e31d 100644
--- a/src/common/threadpool.cpp
+++ b/src/common/threadpool.cpp
@@ -51,13 +51,22 @@ threadpool::threadpool(unsigned int max_threads) : running(true), active(0) {
}
threadpool::~threadpool() {
+ try
{
const boost::unique_lock<boost::mutex> lock(mutex);
running = false;
has_work.notify_all();
}
+ catch (...)
+ {
+ // if the lock throws, we're just do it without a lock and hope,
+ // since the alternative is terminate
+ running = false;
+ has_work.notify_all();
+ }
for (size_t i = 0; i<threads.size(); i++) {
- threads[i].join();
+ try { threads[i].join(); }
+ catch (...) { /* ignore */ }
}
}
@@ -90,11 +99,13 @@ unsigned int threadpool::get_max_concurrency() const {
threadpool::waiter::~waiter()
{
+ try
{
boost::unique_lock<boost::mutex> lock(mt);
if (num)
MERROR("wait should have been called before waiter dtor - waiting now");
}
+ catch (...) { /* ignore */ }
try
{
wait(NULL);
diff --git a/src/common/util.cpp b/src/common/util.cpp
index c56c77505..f91230528 100644
--- a/src/common/util.cpp
+++ b/src/common/util.cpp
@@ -28,6 +28,7 @@
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+#include <unistd.h>
#include <cstdio>
#ifdef __GLIBC__
@@ -233,7 +234,7 @@ namespace tools
MERROR("Failed to open " << filename << ": " << std::error_code(GetLastError(), std::system_category()));
}
#else
- m_fd = open(filename.c_str(), O_RDONLY | O_CREAT, 0666);
+ m_fd = open(filename.c_str(), O_RDONLY | O_CREAT | O_CLOEXEC, 0666);
if (m_fd != -1)
{
if (flock(m_fd, LOCK_EX | LOCK_NB) == -1)
@@ -939,4 +940,51 @@ std::string get_nix_version_display_string()
}
return newval;
}
+
+#ifdef _WIN32
+ std::string input_line_win()
+ {
+ HANDLE hConIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
+ DWORD oldMode;
+
+ FlushConsoleInputBuffer(hConIn);
+ GetConsoleMode(hConIn, &oldMode);
+ SetConsoleMode(hConIn, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
+
+ wchar_t buffer[1024];
+ DWORD read;
+
+ ReadConsoleW(hConIn, buffer, sizeof(buffer)/sizeof(wchar_t)-1, &read, nullptr);
+ buffer[read] = 0;
+
+ SetConsoleMode(hConIn, oldMode);
+ CloseHandle(hConIn);
+
+ int size_needed = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, NULL, 0, NULL, NULL);
+ std::string buf(size_needed, '\0');
+ WideCharToMultiByte(CP_UTF8, 0, buffer, -1, &buf[0], size_needed, NULL, NULL);
+ buf.pop_back(); //size_needed includes null that we needed to have space for
+ return buf;
+ }
+#endif
+
+ void closefrom(int fd)
+ {
+#if defined __FreeBSD__ || defined __OpenBSD__ || defined __NetBSD__ || defined __DragonFly__
+ ::closefrom(fd);
+#else
+#if defined __GLIBC__
+ const int sc_open_max = sysconf(_SC_OPEN_MAX);
+ const int MAX_FDS = std::min(65536, sc_open_max);
+#else
+ const int MAX_FDS = 65536;
+#endif
+ while (fd < MAX_FDS)
+ {
+ close(fd);
+ ++fd;
+ }
+#endif
+ }
+
}
diff --git a/src/common/util.h b/src/common/util.h
index 0e0b50520..e793a42b5 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -235,4 +235,9 @@ namespace tools
boost::optional<std::pair<uint32_t, uint32_t>> parse_subaddress_lookahead(const std::string& str);
std::string glob_to_regex(const std::string &val);
+#ifdef _WIN32
+ std::string input_line_win();
+#endif
+
+ void closefrom(int fd);
}
diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c
index 8fcd2138e..b5946036e 100644
--- a/src/crypto/keccak.c
+++ b/src/crypto/keccak.c
@@ -5,6 +5,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
+#include "common/int-util.h"
#include "hash-ops.h"
#include "keccak.h"
@@ -105,7 +106,7 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen)
for ( ; inlen >= rsiz; inlen -= rsiz, in += rsiz) {
for (i = 0; i < rsizw; i++)
- st[i] ^= ((uint64_t *) in)[i];
+ st[i] ^= swap64le(((uint64_t *) in)[i]);
keccakf(st, KECCAK_ROUNDS);
}
@@ -121,11 +122,15 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen)
temp[rsiz - 1] |= 0x80;
for (i = 0; i < rsizw; i++)
- st[i] ^= ((uint64_t *) temp)[i];
+ st[i] ^= swap64le(((uint64_t *) temp)[i]);
keccakf(st, KECCAK_ROUNDS);
- memcpy(md, st, mdlen);
+ if (((size_t)mdlen % sizeof(uint64_t)) != 0)
+ {
+ local_abort("Bad keccak use");
+ }
+ memcpy_swap64le(md, st, mdlen/sizeof(uint64_t));
}
void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md)
diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c
index 914ba6dc0..40cfb0461 100644
--- a/src/crypto/slow-hash.c
+++ b/src/crypto/slow-hash.c
@@ -198,6 +198,22 @@ extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *exp
} while (0)
#endif
+#define VARIANT2_2_PORTABLE() \
+ if (variant >= 2) { \
+ xor_blocks(long_state + (j ^ 0x10), d); \
+ xor_blocks(d, long_state + (j ^ 0x20)); \
+ }
+
+#define VARIANT2_2() \
+ do if (variant >= 2) \
+ { \
+ *U64(hp_state + (j ^ 0x10)) ^= hi; \
+ *(U64(hp_state + (j ^ 0x10)) + 1) ^= lo; \
+ hi ^= *U64(hp_state + (j ^ 0x20)); \
+ lo ^= *(U64(hp_state + (j ^ 0x20)) + 1); \
+ } while (0)
+
+
#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
@@ -282,6 +298,7 @@ extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *exp
b[0] = p[0]; b[1] = p[1]; \
VARIANT2_INTEGER_MATH_SSE2(b, c); \
__mul(); \
+ VARIANT2_2(); \
VARIANT2_SHUFFLE_ADD_SSE2(hp_state, j); \
a[0] += hi; a[1] += lo; \
p = U64(&hp_state[j]); \
@@ -884,6 +901,7 @@ union cn_slow_hash_state
b[0] = p[0]; b[1] = p[1]; \
VARIANT2_PORTABLE_INTEGER_MATH(b, c); \
__mul(); \
+ VARIANT2_2(); \
VARIANT2_SHUFFLE_ADD_NEON(hp_state, j); \
a[0] += hi; a[1] += lo; \
p = U64(&hp_state[j]); \
@@ -1022,10 +1040,37 @@ STATIC INLINE void aes_pseudo_round_xor(const uint8_t *in, uint8_t *out, const u
}
}
+#ifdef FORCE_USE_HEAP
+STATIC INLINE void* aligned_malloc(size_t size, size_t align)
+{
+ void *result;
+#ifdef _MSC_VER
+ result = _aligned_malloc(size, align);
+#else
+ if (posix_memalign(&result, align, size)) result = NULL;
+#endif
+ return result;
+}
+
+STATIC INLINE void aligned_free(void *ptr)
+{
+#ifdef _MSC_VER
+ _aligned_free(ptr);
+#else
+ free(ptr);
+#endif
+}
+#endif /* FORCE_USE_HEAP */
+
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed)
{
RDATA_ALIGN16 uint8_t expandedKey[240];
+
+#ifndef FORCE_USE_HEAP
RDATA_ALIGN16 uint8_t hp_state[MEMORY];
+#else
+ uint8_t *hp_state = (uint8_t *)aligned_malloc(MEMORY,16);
+#endif
uint8_t text[INIT_SIZE_BYTE];
RDATA_ALIGN16 uint64_t a[2];
@@ -1111,6 +1156,10 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int
memcpy(state.init, text, INIT_SIZE_BYTE);
hash_permutation(&state.hs);
extra_hashes[state.hs.b[0] & 3](&state, 200, hash);
+
+#ifdef FORCE_USE_HEAP
+ aligned_free(hp_state);
+#endif
}
#else /* aarch64 && crypto */
@@ -1252,8 +1301,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int
#ifndef FORCE_USE_HEAP
uint8_t long_state[MEMORY];
#else
- uint8_t *long_state = NULL;
- long_state = (uint8_t *)malloc(MEMORY);
+ uint8_t *long_state = (uint8_t *)malloc(MEMORY);
#endif
if (prehashed) {
@@ -1305,6 +1353,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int
VARIANT2_PORTABLE_INTEGER_MATH(c, c1);
mul(c1, c, d);
+ VARIANT2_2_PORTABLE();
VARIANT2_PORTABLE_SHUFFLE_ADD(long_state, j);
sum_half_blocks(a, d);
swap_blocks(a, c);
@@ -1430,7 +1479,12 @@ union cn_slow_hash_state {
#pragma pack(pop)
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed) {
+#ifndef FORCE_USE_HEAP
uint8_t long_state[MEMORY];
+#else
+ uint8_t *long_state = (uint8_t *)malloc(MEMORY);
+#endif
+
union cn_slow_hash_state state;
uint8_t text[INIT_SIZE_BYTE];
uint8_t a[AES_BLOCK_SIZE];
@@ -1486,6 +1540,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int
copy_block(c2, &long_state[j]);
VARIANT2_PORTABLE_INTEGER_MATH(c2, c1);
mul(c1, c2, d);
+ VARIANT2_2_PORTABLE();
VARIANT2_PORTABLE_SHUFFLE_ADD(long_state, j);
swap_blocks(a, c1);
sum_half_blocks(c1, d);
@@ -1514,6 +1569,10 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int
/*memcpy(hash, &state, 32);*/
extra_hashes[state.hs.b[0] & 3](&state, 200, hash);
oaes_free((OAES_CTX **) &aes_ctx);
+
+#ifdef FORCE_USE_HEAP
+ free(long_state);
+#endif
}
#endif
diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h
index d4558ef7b..b0eabb0aa 100644
--- a/src/cryptonote_basic/cryptonote_basic.h
+++ b/src/cryptonote_basic/cryptonote_basic.h
@@ -47,7 +47,6 @@
#include "crypto/crypto.h"
#include "crypto/hash.h"
#include "misc_language.h"
-#include "tx_extra.h"
#include "ringct/rctTypes.h"
#include "device/device.hpp"
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index 5fcfa33f6..e26aac76b 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -199,6 +199,16 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
+ bool parse_and_validate_tx_prefix_from_blob(const blobdata& tx_blob, transaction_prefix& tx)
+ {
+ std::stringstream ss;
+ ss << tx_blob;
+ binary_archive<false> ba(ss);
+ bool r = ::serialization::serialize_noeof(ba, tx);
+ CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction prefix from blob");
+ return true;
+ }
+ //---------------------------------------------------------------
bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash)
{
std::stringstream ss;
@@ -435,6 +445,91 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
+ template<typename T>
+ static bool pick(binary_archive<true> &ar, std::vector<tx_extra_field> &fields, uint8_t tag)
+ {
+ std::vector<tx_extra_field>::iterator it;
+ while ((it = std::find_if(fields.begin(), fields.end(), [](const tx_extra_field &f) { return f.type() == typeid(T); })) != fields.end())
+ {
+ bool r = ::do_serialize(ar, tag);
+ CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra field");
+ r = ::do_serialize(ar, boost::get<T>(*it));
+ CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra field");
+ fields.erase(it);
+ }
+ return true;
+ }
+ //---------------------------------------------------------------
+ bool sort_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<uint8_t> &sorted_tx_extra, bool allow_partial)
+ {
+ std::vector<tx_extra_field> tx_extra_fields;
+
+ if(tx_extra.empty())
+ {
+ sorted_tx_extra.clear();
+ return true;
+ }
+
+ std::string extra_str(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size());
+ std::istringstream iss(extra_str);
+ binary_archive<false> ar(iss);
+
+ bool eof = false;
+ size_t processed = 0;
+ while (!eof)
+ {
+ tx_extra_field field;
+ bool r = ::do_serialize(ar, field);
+ if (!r)
+ {
+ MWARNING("failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size())));
+ if (!allow_partial)
+ return false;
+ break;
+ }
+ tx_extra_fields.push_back(field);
+ processed = iss.tellg();
+
+ std::ios_base::iostate state = iss.rdstate();
+ eof = (EOF == iss.peek());
+ iss.clear(state);
+ }
+ if (!::serialization::check_stream_state(ar))
+ {
+ MWARNING("failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size())));
+ if (!allow_partial)
+ return false;
+ }
+ MTRACE("Sorted " << processed << "/" << tx_extra.size());
+
+ std::ostringstream oss;
+ binary_archive<true> nar(oss);
+
+ // sort by:
+ if (!pick<tx_extra_pub_key>(nar, tx_extra_fields, TX_EXTRA_TAG_PUBKEY)) return false;
+ if (!pick<tx_extra_additional_pub_keys>(nar, tx_extra_fields, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS)) return false;
+ if (!pick<tx_extra_nonce>(nar, tx_extra_fields, TX_EXTRA_NONCE)) return false;
+ if (!pick<tx_extra_merge_mining_tag>(nar, tx_extra_fields, TX_EXTRA_MERGE_MINING_TAG)) return false;
+ if (!pick<tx_extra_mysterious_minergate>(nar, tx_extra_fields, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG)) return false;
+ if (!pick<tx_extra_padding>(nar, tx_extra_fields, TX_EXTRA_TAG_PADDING)) return false;
+
+ // if not empty, someone added a new type and did not add a case above
+ if (!tx_extra_fields.empty())
+ {
+ MERROR("tx_extra_fields not empty after sorting, someone forgot to add a case above");
+ return false;
+ }
+
+ std::string oss_str = oss.str();
+ if (allow_partial && processed < tx_extra.size())
+ {
+ MDEBUG("Appending unparsed data");
+ oss_str += std::string((const char*)tx_extra.data() + processed, tx_extra.size() - processed);
+ }
+ sorted_tx_extra = std::vector<uint8_t>(oss_str.begin(), oss_str.end());
+ return true;
+ }
+ //---------------------------------------------------------------
crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index)
{
std::vector<tx_extra_field> tx_extra_fields;
diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h
index bf71eb591..8d33b1ca4 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.h
+++ b/src/cryptonote_basic/cryptonote_format_utils.h
@@ -31,6 +31,7 @@
#pragma once
#include "blobdatatype.h"
#include "cryptonote_basic_impl.h"
+#include "tx_extra.h"
#include "account.h"
#include "subaddress_index.h"
#include "include_base_utils.h"
@@ -48,6 +49,7 @@ namespace cryptonote
//---------------------------------------------------------------
void get_transaction_prefix_hash(const transaction_prefix& tx, crypto::hash& h);
crypto::hash get_transaction_prefix_hash(const transaction_prefix& tx);
+ bool parse_and_validate_tx_prefix_from_blob(const blobdata& tx_blob, transaction_prefix& tx);
bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash);
bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx);
bool parse_and_validate_tx_base_from_blob(const blobdata& tx_blob, transaction& tx);
@@ -64,6 +66,7 @@ namespace cryptonote
}
bool parse_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<tx_extra_field>& tx_extra_fields);
+ bool sort_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<uint8_t> &sorted_tx_extra, bool allow_partial = false);
crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index = 0);
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);
diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp
index dfe456ef4..d0b03593e 100644
--- a/src/cryptonote_basic/miner.cpp
+++ b/src/cryptonote_basic/miner.cpp
@@ -121,7 +121,8 @@ namespace cryptonote
//-----------------------------------------------------------------------------------------------------
miner::~miner()
{
- stop();
+ try { stop(); }
+ catch (...) { /* ignore */ }
}
//-----------------------------------------------------------------------------------------------------
bool miner::set_block_template(const block& bl, const difficulty_type& di, uint64_t height)
@@ -198,8 +199,9 @@ namespace cryptonote
{
uint64_t total_hr = std::accumulate(m_last_hash_rates.begin(), m_last_hash_rates.end(), 0);
float hr = static_cast<float>(total_hr)/static_cast<float>(m_last_hash_rates.size());
+ const auto flags = std::cout.flags();
const auto precision = std::cout.precision();
- std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << precision << ENDL;
+ std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << std::setiosflags(flags) << std::setprecision(precision) << ENDL;
}
}
m_last_hr_merge_time = misc_utils::get_tick_count();
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index e94024ae5..5c6819fa9 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -52,6 +52,7 @@
#include "cryptonote_core.h"
#include "ringct/rctSigs.h"
#include "common/perf_timer.h"
+#include "common/notify.h"
#if defined(PER_BLOCK_CHECKPOINT)
#include "blocks/blocks.h"
#endif
@@ -137,8 +138,8 @@ static const struct {
{ 6, 971400, 0, 1501709789 },
{ 7, 1057027, 0, 1512211236 },
- { 8, 1057058, 0, 1515967497 },
- { 9, 1057778, 0, 1515967498 },
+ { 8, 1057058, 0, 1533211200 },
+ { 9, 1057778, 0, 1533297600 },
};
static const uint64_t testnet_hard_fork_version_1_till = 624633;
@@ -158,6 +159,8 @@ static const struct {
{ 5, 35000, 0, 1521360000 },
{ 6, 36000, 0, 1521480000 },
{ 7, 37000, 0, 1521600000 },
+ { 8, 176456, 0, 1537821770 },
+ { 9, 177176, 0, 1537821771 },
};
//------------------------------------------------------------------
@@ -341,6 +344,9 @@ uint64_t Blockchain::get_current_blockchain_height() const
bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline, const cryptonote::test_options *test_options, difficulty_type fixed_difficulty)
{
LOG_PRINT_L3("Blockchain::" << __func__);
+
+ CHECK_AND_ASSERT_MES(nettype != FAKECHAIN || test_options, false, "fake chain network type used without options");
+
CRITICAL_REGION_LOCAL(m_tx_pool);
CRITICAL_REGION_LOCAL1(m_blockchain_lock);
@@ -1827,15 +1833,10 @@ bool Blockchain::get_output_distribution(uint64_t amount, uint64_t from_height,
{
std::vector<uint64_t> heights;
heights.reserve(to_height + 1 - start_height);
- uint64_t real_start_height = start_height > 0 ? start_height-1 : start_height;
- for (uint64_t h = real_start_height; h <= to_height; ++h)
+ for (uint64_t h = start_height; h <= to_height; ++h)
heights.push_back(h);
distribution = m_db->get_block_cumulative_rct_outputs(heights);
- if (start_height > 0)
- {
- base = distribution[0];
- distribution.erase(distribution.begin());
- }
+ base = 0;
return true;
}
else
@@ -2375,7 +2376,7 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context
const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type);
if (bulletproof || !tx.rct_signatures.p.bulletproofs.empty())
{
- MERROR("Bulletproofs are not allowed before v8");
+ MERROR_VER("Bulletproofs are not allowed before v8");
tvc.m_invalid_output = true;
return false;
}
@@ -2388,7 +2389,7 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context
const bool borromean = rct::is_rct_borromean(tx.rct_signatures.type);
if (borromean)
{
- MERROR("Borromean range proofs are not allowed after v8");
+ MERROR_VER("Borromean range proofs are not allowed after v8");
tvc.m_invalid_output = true;
return false;
}
@@ -3564,6 +3565,10 @@ leave:
get_difficulty_for_next_block(); // just to cache it
invalidate_block_template_cache();
+ std::shared_ptr<tools::Notify> block_notify = m_block_notify;
+ if (block_notify)
+ block_notify->notify(epee::string_tools::pod_to_hex(id).c_str());
+
return true;
}
//------------------------------------------------------------------
@@ -4410,7 +4415,7 @@ void Blockchain::cancel()
}
#if defined(PER_BLOCK_CHECKPOINT)
-static const char expected_block_hashes_hash[] = "0924bc1c47aae448321fde949554be192878dd800e6489379865218f84eacbca";
+static const char expected_block_hashes_hash[] = "954cb2bbfa2fe6f74b2cdd22a1a4c767aea249ad47ad4f7c9445f0f03260f511";
void Blockchain::load_compiled_in_block_hashes()
{
const bool testnet = m_nettype == TESTNET;
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index f56068db8..8ebe7b5ce 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -55,6 +55,8 @@
#include "cryptonote_basic/hardfork.h"
#include "blockchain_db/blockchain_db.h"
+namespace tools { class Notify; }
+
namespace cryptonote
{
class tx_memory_pool;
@@ -706,6 +708,13 @@ namespace cryptonote
blockchain_db_sync_mode sync_mode, bool fast_sync);
/**
+ * @brief sets a block notify object to call for every new block
+ *
+ * @param notify the notify object to cal at every new block
+ */
+ void set_block_notify(const std::shared_ptr<tools::Notify> &notify) { m_block_notify = notify; }
+
+ /**
* @brief Put DB in safe sync mode
*/
void safesyncmode(const bool onoff);
@@ -1037,6 +1046,8 @@ namespace cryptonote
uint64_t m_btc_expected_reward;
bool m_btc_valid;
+ std::shared_ptr<tools::Notify> m_block_notify;
+
/**
* @brief collects the keys for all outputs being "spent" as an input
*
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 7f8c60f73..735309aa9 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -53,6 +53,7 @@ using namespace epee;
#include "ringct/rctTypes.h"
#include "blockchain_db/blockchain_db.h"
#include "ringct/rctSigs.h"
+#include "common/notify.h"
#include "version.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
@@ -167,6 +168,11 @@ namespace cryptonote
, "Set maximum txpool weight in bytes."
, DEFAULT_TXPOOL_MAX_WEIGHT
};
+ static const command_line::arg_descriptor<std::string> arg_block_notify = {
+ "block-notify"
+ , "Run a program for each new block, '%s' will be replaced by the block hash"
+ , ""
+ };
//-----------------------------------------------------------------------------------------------
core::core(i_cryptonote_protocol* pprotocol):
@@ -181,7 +187,8 @@ namespace cryptonote
m_last_json_checkpoints_update(0),
m_disable_dns_checkpoints(false),
m_update_download(0),
- m_nettype(UNDEFINED)
+ m_nettype(UNDEFINED),
+ m_update_available(false)
{
m_checkpoints_updating.clear();
set_cryptonote_protocol(pprotocol);
@@ -275,6 +282,7 @@ namespace cryptonote
command_line::add_arg(desc, arg_offline);
command_line::add_arg(desc, arg_disable_dns_checkpoints);
command_line::add_arg(desc, arg_max_txpool_weight);
+ command_line::add_arg(desc, arg_block_notify);
miner::init_options(desc);
BlockchainDB::init_options(desc);
@@ -544,6 +552,16 @@ namespace cryptonote
m_blockchain_storage.set_user_options(blocks_threads,
sync_on_blocks, sync_threshold, sync_mode, fast_sync);
+ try
+ {
+ if (!command_line::is_arg_defaulted(vm, arg_block_notify))
+ m_blockchain_storage.set_block_notify(std::shared_ptr<tools::Notify>(new tools::Notify(command_line::get_arg(vm, arg_block_notify).c_str())));
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to parse block notify spec");
+ }
+
const std::pair<uint8_t, uint64_t> regtest_hard_forks[3] = {std::make_pair(1, 0), std::make_pair(Blockchain::get_hard_fork_heights(MAINNET).back().version, 1), std::make_pair(0, 0)};
const cryptonote::test_options regtest_test_options = {
regtest_hard_forks
@@ -838,16 +856,19 @@ namespace cryptonote
}
waiter.wait(&tpool);
it = tx_blobs.begin();
+ std::vector<bool> already_have(tx_blobs.size(), false);
for (size_t i = 0; i < tx_blobs.size(); i++, ++it) {
if (!results[i].res)
continue;
if(m_mempool.have_tx(results[i].hash))
{
LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool");
+ already_have[i] = true;
}
else if(m_blockchain_storage.have_tx(results[i].hash))
{
LOG_PRINT_L2("tx " << results[i].hash << " already have transaction in blockchain");
+ already_have[i] = true;
}
else
{
@@ -869,7 +890,7 @@ namespace cryptonote
std::vector<tx_verification_batch_info> tx_info;
tx_info.reserve(tx_blobs.size());
for (size_t i = 0; i < tx_blobs.size(); i++) {
- if (!results[i].res)
+ if (!results[i].res || already_have[i])
continue;
tx_info.push_back({&results[i].tx, results[i].hash, tvc[i], results[i].res});
}
@@ -879,6 +900,8 @@ namespace cryptonote
bool ok = true;
it = tx_blobs.begin();
for (size_t i = 0; i < tx_blobs.size(); i++, ++it) {
+ if (already_have[i])
+ continue;
if (!results[i].res)
{
ok = false;
@@ -1542,10 +1565,14 @@ namespace cryptonote
return false;
if (tools::vercmp(version.c_str(), MONERO_VERSION) <= 0)
+ {
+ m_update_available = false;
return true;
+ }
std::string url = tools::get_update_url(software, subdir, buildtag, version, true);
MCLOG_CYAN(el::Level::Info, "global", "Version " << version << " of " << software << " for " << buildtag << " is available: " << url << ", SHA256 hash " << hash);
+ m_update_available = true;
if (check_updates_level == UPDATES_NOTIFY)
return true;
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index b2be05bf4..225edc137 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -739,6 +739,16 @@ namespace cryptonote
network_type get_nettype() const { return m_nettype; };
/**
+ * @brief check whether an update is known to be available or not
+ *
+ * This does not actually trigger a check, but returns the result
+ * of the last check
+ *
+ * @return whether an update is known to be available or not
+ */
+ bool is_update_available() const { return m_update_available; }
+
+ /**
* @brief get whether fluffy blocks are enabled
*
* @return whether fluffy blocks are enabled
@@ -974,6 +984,8 @@ namespace cryptonote
network_type m_nettype; //!< which network are we on?
+ std::atomic<bool> m_update_available;
+
std::string m_checkpoints_path; //!< path to json checkpoints file
time_t m_last_dns_checkpoints_update; //!< time when dns checkpoints were last updated
time_t m_last_json_checkpoints_update; //!< time when json checkpoints were last updated
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index fb2af9ceb..4fc2736a6 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -38,6 +38,7 @@ using namespace epee;
#include "cryptonote_tx_utils.h"
#include "cryptonote_config.h"
#include "cryptonote_basic/miner.h"
+#include "cryptonote_basic/tx_extra.h"
#include "crypto/crypto.h"
#include "crypto/hash.h"
#include "ringct/rctSigs.h"
@@ -84,6 +85,8 @@ namespace cryptonote
if(!extra_nonce.empty())
if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce))
return false;
+ if (!sort_tx_extra(tx.extra, tx.extra))
+ return false;
txin_gen in;
in.height = height;
@@ -127,7 +130,7 @@ namespace cryptonote
out_amounts[1] += out_amounts[0];
for (size_t n = 1; n < out_amounts.size(); ++n)
out_amounts[n - 1] = out_amounts[n];
- out_amounts.resize(out_amounts.size() - 1);
+ out_amounts.pop_back();
}
}
else
@@ -434,6 +437,9 @@ namespace cryptonote
add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys);
}
+ if (!sort_tx_extra(tx.extra, tx.extra))
+ return false;
+
//check money
if(summary_outs_money > summary_inputs_money )
{
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index a725eac6e..553a22298 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -250,7 +250,7 @@ namespace cryptonote
CRITICAL_REGION_LOCAL1(m_blockchain);
LockedTXN lock(m_blockchain);
m_blockchain.add_txpool_tx(tx, meta);
- if (!insert_key_images(tx, kept_by_block))
+ if (!insert_key_images(tx, id, kept_by_block))
return false;
m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)tx_weight, receive_time), id);
}
@@ -290,9 +290,10 @@ namespace cryptonote
{
CRITICAL_REGION_LOCAL1(m_blockchain);
LockedTXN lock(m_blockchain);
- m_blockchain.remove_txpool_tx(get_transaction_hash(tx));
+ const crypto::hash txid = get_transaction_hash(tx);
+ m_blockchain.remove_txpool_tx(txid);
m_blockchain.add_txpool_tx(tx, meta);
- if (!insert_key_images(tx, kept_by_block))
+ if (!insert_key_images(tx, txid, kept_by_block))
return false;
m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)tx_weight, receive_time), id);
}
@@ -371,8 +372,8 @@ namespace cryptonote
continue;
}
cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid);
- cryptonote::transaction tx;
- if (!parse_and_validate_tx_from_blob(txblob, tx))
+ cryptonote::transaction_prefix tx;
+ if (!parse_and_validate_tx_prefix_from_blob(txblob, tx))
{
MERROR("Failed to parse tx from txpool");
return;
@@ -381,7 +382,7 @@ namespace cryptonote
MINFO("Pruning tx " << txid << " from txpool: weight: " << it->first.second << ", fee/byte: " << it->first.first);
m_blockchain.remove_txpool_tx(txid);
m_txpool_weight -= it->first.second;
- remove_transaction_keyimages(tx);
+ remove_transaction_keyimages(tx, txid);
MINFO("Pruned tx " << txid << " from txpool: weight: " << it->first.second << ", fee/byte: " << it->first.first);
m_txs_by_fee_and_receive_time.erase(it--);
changed = true;
@@ -398,11 +399,10 @@ namespace cryptonote
MINFO("Pool weight after pruning is larger than limit: " << m_txpool_weight << "/" << bytes);
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::insert_key_images(const transaction &tx, bool kept_by_block)
+ bool tx_memory_pool::insert_key_images(const transaction_prefix &tx, const crypto::hash &id, bool kept_by_block)
{
for(const auto& in: tx.vin)
{
- const crypto::hash id = get_transaction_hash(tx);
CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false);
std::unordered_set<crypto::hash>& kei_image_set = m_spent_key_images[txin.k_image];
CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: kept_by_block=" << kept_by_block
@@ -418,19 +418,17 @@ namespace cryptonote
//FIXME: Can return early before removal of all of the key images.
// At the least, need to make sure that a false return here
// is treated properly. Should probably not return early, however.
- bool tx_memory_pool::remove_transaction_keyimages(const transaction& tx)
+ bool tx_memory_pool::remove_transaction_keyimages(const transaction_prefix& tx, const crypto::hash &actual_hash)
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
// ND: Speedup
- // 1. Move transaction hash calcuation outside of loop. ._.
- crypto::hash actual_hash = get_transaction_hash(tx);
for(const txin_v& vi: tx.vin)
{
CHECKED_GET_SPECIFIC_VARIANT(vi, const txin_to_key, txin, false);
auto it = m_spent_key_images.find(txin.k_image);
CHECK_AND_ASSERT_MES(it != m_spent_key_images.end(), false, "failed to find transaction input in key images. img=" << txin.k_image << ENDL
- << "transaction id = " << get_transaction_hash(tx));
+ << "transaction id = " << actual_hash);
std::unordered_set<crypto::hash>& key_image_set = it->second;
CHECK_AND_ASSERT_MES(key_image_set.size(), false, "empty key_image set, img=" << txin.k_image << ENDL
<< "transaction id = " << actual_hash);
@@ -483,7 +481,7 @@ namespace cryptonote
// remove first, in case this throws, so key images aren't removed
m_blockchain.remove_txpool_tx(id);
m_txpool_weight -= tx_weight;
- remove_transaction_keyimages(tx);
+ remove_transaction_keyimages(tx, id);
}
catch (const std::exception &e)
{
@@ -515,7 +513,7 @@ namespace cryptonote
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
- std::unordered_set<crypto::hash> remove;
+ std::list<std::pair<crypto::hash, uint64_t>> remove;
m_blockchain.for_all_txpool_txes([this, &remove](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata*) {
uint64_t tx_age = time(nullptr) - meta.receive_time;
@@ -533,7 +531,7 @@ namespace cryptonote
m_txs_by_fee_and_receive_time.erase(sorted_it);
}
m_timed_out_transactions.insert(txid);
- remove.insert(txid);
+ remove.push_back(std::make_pair(txid, meta.weight));
}
return true;
}, false);
@@ -541,13 +539,14 @@ namespace cryptonote
if (!remove.empty())
{
LockedTXN lock(m_blockchain);
- for (const crypto::hash &txid: remove)
+ for (const std::pair<crypto::hash, uint64_t> &entry: remove)
{
+ const crypto::hash &txid = entry.first;
try
{
cryptonote::blobdata bd = m_blockchain.get_txpool_tx_blob(txid);
- cryptonote::transaction tx;
- if (!parse_and_validate_tx_from_blob(bd, tx))
+ cryptonote::transaction_prefix tx;
+ if (!parse_and_validate_tx_prefix_from_blob(bd, tx))
{
MERROR("Failed to parse tx from txpool");
// continue
@@ -556,8 +555,8 @@ namespace cryptonote
{
// remove first, so we only remove key images if the tx removal succeeds
m_blockchain.remove_txpool_tx(txid);
- m_txpool_weight -= get_transaction_weight(tx, bd.size());
- remove_transaction_keyimages(tx);
+ m_txpool_weight -= entry.second;
+ remove_transaction_keyimages(tx, txid);
}
}
catch (const std::exception &e)
@@ -1041,7 +1040,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::have_key_images(const std::unordered_set<crypto::key_image>& k_images, const transaction& tx)
+ bool tx_memory_pool::have_key_images(const std::unordered_set<crypto::key_image>& k_images, const transaction_prefix& tx)
{
for(size_t i = 0; i!= tx.vin.size(); i++)
{
@@ -1052,7 +1051,7 @@ namespace cryptonote
return false;
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::append_key_images(std::unordered_set<crypto::key_image>& k_images, const transaction& tx)
+ bool tx_memory_pool::append_key_images(std::unordered_set<crypto::key_image>& k_images, const transaction_prefix& tx)
{
for(size_t i = 0; i!= tx.vin.size(); i++)
{
@@ -1301,7 +1300,7 @@ namespace cryptonote
// remove tx from db first
m_blockchain.remove_txpool_tx(txid);
m_txpool_weight -= get_transaction_weight(tx, txblob.size());
- remove_transaction_keyimages(tx);
+ remove_transaction_keyimages(tx, txid);
auto sorted_it = find_tx_in_sorted_container(txid);
if (sorted_it == m_txs_by_fee_and_receive_time.end())
{
@@ -1344,13 +1343,14 @@ namespace cryptonote
bool r = m_blockchain.for_all_txpool_txes([this, &remove, kept](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd) {
if (!!kept != !!meta.kept_by_block)
return true;
- cryptonote::transaction tx;
- if (!parse_and_validate_tx_from_blob(*bd, tx))
+ cryptonote::transaction_prefix tx;
+ if (!parse_and_validate_tx_prefix_from_blob(*bd, tx))
{
MWARNING("Failed to parse tx from txpool, removing");
remove.push_back(txid);
+ return true;
}
- if (!insert_key_images(tx, meta.kept_by_block))
+ if (!insert_key_images(tx, txid, meta.kept_by_block))
{
MFATAL("Failed to insert key images from txpool tx");
return false;
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index 892cadc69..7a0cc23bf 100644
--- a/src/cryptonote_core/tx_pool.h
+++ b/src/cryptonote_core/tx_pool.h
@@ -434,7 +434,7 @@ namespace cryptonote
*
* @return true on success, false on error
*/
- bool insert_key_images(const transaction &tx, bool kept_by_block);
+ bool insert_key_images(const transaction_prefix &tx, const crypto::hash &txid, bool kept_by_block);
/**
* @brief remove old transactions from the pool
@@ -478,10 +478,11 @@ namespace cryptonote
* a transaction from the pool.
*
* @param tx the transaction
+ * @param txid the transaction's hash
*
* @return false if any key images to be removed cannot be found, otherwise true
*/
- bool remove_transaction_keyimages(const transaction& tx);
+ bool remove_transaction_keyimages(const transaction_prefix& tx, const crypto::hash &txid);
/**
* @brief check if any of a transaction's spent key images are present in a given set
@@ -491,7 +492,7 @@ namespace cryptonote
*
* @return true if any key images present in the set, otherwise false
*/
- static bool have_key_images(const std::unordered_set<crypto::key_image>& kic, const transaction& tx);
+ static bool have_key_images(const std::unordered_set<crypto::key_image>& kic, const transaction_prefix& tx);
/**
* @brief append the key images from a transaction to the given set
@@ -501,7 +502,7 @@ namespace cryptonote
*
* @return false if any append fails, otherwise true
*/
- static bool append_key_images(std::unordered_set<crypto::key_image>& kic, const transaction& tx);
+ static bool append_key_images(std::unordered_set<crypto::key_image>& kic, const transaction_prefix& tx);
/**
* @brief check if a transaction is a valid candidate for inclusion in a block
@@ -509,11 +510,11 @@ namespace cryptonote
* @param txd the transaction to check (and info about it)
* @param txid the txid of the transaction to check
* @param txblob the transaction blob to check
- * @param tx the parsed transaction, if successful
+ * @param tx the parsed transaction prefix, if successful
*
* @return true if the transaction is good to go, otherwise false
*/
- bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata &txblob, transaction &tx) const;
+ bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata &txblob, transaction&tx) const;
/**
* @brief mark all transactions double spending the one passed
diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt
index b1c4b711d..f645836a4 100644
--- a/src/daemon/CMakeLists.txt
+++ b/src/daemon/CMakeLists.txt
@@ -32,6 +32,8 @@ if(PER_BLOCK_CHECKPOINT)
add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} --target=x86_64-apple-darwin11 -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*)
elseif(APPLE AND NOT DEPENDS)
add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*)
+ elseif(LINUX_32)
+ add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat)
else()
add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat)
endif()
diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp
index ea24e32eb..49d6d49cf 100644
--- a/src/daemon/daemon.cpp
+++ b/src/daemon/daemon.cpp
@@ -136,7 +136,19 @@ bool t_daemon::run(bool interactive)
{
throw std::runtime_error{"Can't run stopped daemon"};
}
- tools::signal_handler::install(std::bind(&daemonize::t_daemon::stop_p2p, this));
+
+ std::atomic<bool> stop(false), shutdown(false);
+ boost::thread stop_thread = boost::thread([&stop, &shutdown, this] {
+ while (!stop)
+ epee::misc_utils::sleep_no_w(100);
+ if (shutdown)
+ this->stop_p2p();
+ });
+ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){
+ stop = true;
+ stop_thread.join();
+ });
+ tools::signal_handler::install([&stop, &shutdown](int){ stop = shutdown = true; });
try
{
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index 6b6c88907..6464d372f 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -442,7 +442,8 @@ bool t_rpc_command_executor::show_status() {
}
}
- tools::success_msg_writer() << boost::format("Height: %llu/%llu (%.1f%%) on %s%s, %s, net hash %s, v%u%s, %s, %u(out)+%u(in) connections, uptime %ud %uh %um %us")
+ std::stringstream str;
+ str << boost::format("Height: %llu/%llu (%.1f%%) on %s%s, %s, net hash %s, v%u%s, %s, %u(out)+%u(in) connections")
% (unsigned long long)ires.height
% (unsigned long long)net_height
% get_sync_percentage(ires)
@@ -455,12 +456,21 @@ bool t_rpc_command_executor::show_status() {
% (hfres.state == cryptonote::HardFork::Ready ? "up to date" : hfres.state == cryptonote::HardFork::UpdateNeeded ? "update needed" : "out of date, likely forked")
% (unsigned)ires.outgoing_connections_count
% (unsigned)ires.incoming_connections_count
- % (unsigned int)floor(uptime / 60.0 / 60.0 / 24.0)
- % (unsigned int)floor(fmod((uptime / 60.0 / 60.0), 24.0))
- % (unsigned int)floor(fmod((uptime / 60.0), 60.0))
- % (unsigned int)fmod(uptime, 60.0)
;
+ // restricted RPC does not disclose start time
+ if (ires.start_time)
+ {
+ str << boost::format(", uptime %ud %uh %um %us")
+ % (unsigned int)floor(uptime / 60.0 / 60.0 / 24.0)
+ % (unsigned int)floor(fmod((uptime / 60.0 / 60.0), 24.0))
+ % (unsigned int)floor(fmod((uptime / 60.0), 60.0))
+ % (unsigned int)fmod(uptime, 60.0)
+ ;
+ }
+
+ tools::success_msg_writer() << str.str();
+
return true;
}
diff --git a/src/daemonizer/windows_daemonizer.inl b/src/daemonizer/windows_daemonizer.inl
index 8077f29fb..7e61e3603 100644
--- a/src/daemonizer/windows_daemonizer.inl
+++ b/src/daemonizer/windows_daemonizer.inl
@@ -31,6 +31,7 @@
#include "common/util.h"
#include "daemonizer/windows_service.h"
#include "daemonizer/windows_service_runner.h"
+#include "cryptonote_core/cryptonote_core.h"
#include <shlobj.h>
#include <boost/filesystem/operations.hpp>
diff --git a/src/debug_utilities/object_sizes.cpp b/src/debug_utilities/object_sizes.cpp
index ab8839636..2281d0734 100644
--- a/src/debug_utilities/object_sizes.cpp
+++ b/src/debug_utilities/object_sizes.cpp
@@ -29,6 +29,7 @@
#include <map>
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/tx_extra.h"
+#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_core/blockchain.h"
#include "p2p/p2p_protocol_defs.h"
#include "net/connection_basic.hpp"
diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt
index cc2d20a54..8f446f42a 100644
--- a/src/device/CMakeLists.txt
+++ b/src/device/CMakeLists.txt
@@ -32,18 +32,27 @@ set(device_sources
log.cpp
)
-if(PCSC_FOUND)
- set(device_sources ${device_sources} device_ledger.cpp)
+if(HIDAPI_FOUND)
+ set(device_sources
+ ${device_sources}
+ device_ledger.cpp
+ device_io_hid.cpp
+ )
endif()
set(device_headers
device.hpp
+ device_io.hpp
device_default.hpp
log.hpp
)
-if(PCSC_FOUND)
- set(device_headers ${device_headers} device_ledger.hpp)
+if(HIDAPI_FOUND)
+ set(device_headers
+ ${device_headers}
+ device_ledger.hpp
+ device_io_hid.hpp
+ )
endif()
set(device_private_headers)
@@ -65,7 +74,7 @@ monero_add_library(device
target_link_libraries(device
PUBLIC
- ${PCSC_LIBRARIES}
+ ${HIDAPI_LIBRARIES}
cncrypto
ringct_basic
${OPENSSL_CRYPTO_LIBRARIES}
diff --git a/src/device/device.cpp b/src/device/device.cpp
index 8a8b40061..d5e3031ff 100644
--- a/src/device/device.cpp
+++ b/src/device/device.cpp
@@ -29,7 +29,7 @@
#include "device.hpp"
#include "device_default.hpp"
-#ifdef HAVE_PCSC
+#ifdef WITH_DEVICE_LEDGER
#include "device_ledger.hpp"
#endif
#include "misc_log_ex.h"
@@ -41,13 +41,26 @@ namespace hw {
/* SETUP */
/* ======================================================================= */
- static std::unique_ptr<device_registry> registry;
+ static device_registry *get_device_registry(bool clear = false){
+ static device_registry *registry = new device_registry();
+ if (clear)
+ {
+ delete registry;
+ registry = NULL;
+ }
+ return registry;
+ }
+
+ static void clear_device_registry(){
+ get_device_registry(true);
+ }
device_registry::device_registry(){
hw::core::register_all(registry);
- #ifdef HAVE_PCSC
+ #ifdef WITH_DEVICE_LEDGER
hw::ledger::register_all(registry);
#endif
+ atexit(clear_device_registry);
}
bool device_registry::register_device(const std::string & device_name, device * hw_device){
@@ -80,18 +93,12 @@ namespace hw {
}
device& get_device(const std::string & device_descriptor) {
- if (!registry){
- registry.reset(new device_registry());
- }
-
+ device_registry *registry = get_device_registry();
return registry->get_device(device_descriptor);
}
bool register_device(const std::string & device_name, device * hw_device){
- if (!registry){
- registry.reset(new device_registry());
- }
-
+ device_registry *registry = get_device_registry();
return registry->register_device(device_name, hw_device);
}
diff --git a/src/device/device.hpp b/src/device/device.hpp
index d14b8848c..cb9117650 100644
--- a/src/device/device.hpp
+++ b/src/device/device.hpp
@@ -48,11 +48,12 @@
#include "crypto/chacha.h"
#include "ringct/rctTypes.h"
+
#ifndef USE_DEVICE_LEDGER
#define USE_DEVICE_LEDGER 1
#endif
-#if !defined(HAVE_PCSC)
+#if !defined(HAVE_HIDAPI)
#undef USE_DEVICE_LEDGER
#define USE_DEVICE_LEDGER 0
#endif
@@ -78,7 +79,6 @@ namespace hw {
return false;
}
-
class device {
protected:
std::string name;
@@ -96,6 +96,12 @@ namespace hw {
TRANSACTION_CREATE_FAKE,
TRANSACTION_PARSE
};
+ enum device_type
+ {
+ SOFTWARE = 0,
+ LEDGER = 1
+ };
+
/* ======================================================================= */
/* SETUP/TEARDOWN */
@@ -109,7 +115,9 @@ namespace hw {
virtual bool connect(void) = 0;
virtual bool disconnect(void) = 0;
- virtual bool set_mode(device_mode mode) = 0;
+ virtual bool set_mode(device_mode mode) = 0;
+
+ virtual device_type get_type() const = 0;
/* ======================================================================= */
diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp
index 8d841d9de..5c59a9066 100644
--- a/src/device/device_default.hpp
+++ b/src/device/device_default.hpp
@@ -61,6 +61,8 @@ namespace hw {
bool set_mode(device_mode mode) override;
+ device_type get_type() const override {return device_type::SOFTWARE;};
+
/* ======================================================================= */
/* LOCKER */
/* ======================================================================= */
diff --git a/src/device/device_io.hpp b/src/device/device_io.hpp
new file mode 100644
index 000000000..96163a211
--- /dev/null
+++ b/src/device/device_io.hpp
@@ -0,0 +1,56 @@
+// Copyright (c) 2017-2018, 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
+
+
+namespace hw {
+ namespace io {
+
+ class device_io {
+
+ public:
+
+ device_io() {};
+ ~device_io() {};
+
+ virtual void init() = 0;
+ virtual void release() = 0;
+
+ virtual void connect(void *parms) = 0;
+ virtual void disconnect() = 0;
+ virtual bool connected() const = 0;
+
+ virtual int exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len) = 0;
+ };
+ };
+};
diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp
new file mode 100644
index 000000000..666255cb3
--- /dev/null
+++ b/src/device/device_io_hid.cpp
@@ -0,0 +1,316 @@
+// 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 defined(HAVE_HIDAPI)
+
+#include <boost/scope_exit.hpp>
+#include "log.hpp"
+#include "device_io_hid.hpp"
+
+namespace hw {
+ namespace io {
+
+ #undef MONERO_DEFAULT_LOG_CATEGORY
+ #define MONERO_DEFAULT_LOG_CATEGORY "device.io"
+
+ #define ASSERT_X(exp,msg) CHECK_AND_ASSERT_THROW_MES(exp, msg);
+
+ #define MAX_BLOCK 64
+
+ static std::string safe_hid_error(hid_device *hwdev) {
+ if (hwdev) {
+ return std::string((char*)hid_error(hwdev));
+ }
+ return std::string("NULL device");
+ }
+
+ static std::string safe_hid_path(const hid_device_info *hwdev_info) {
+ if (hwdev_info && hwdev_info->path) {
+ return std::string(hwdev_info->path);
+ }
+ return std::string("NULL path");
+ }
+
+ device_io_hid::device_io_hid(unsigned short c, unsigned char t, unsigned int ps, unsigned int to) :
+ channel(c),
+ tag(t),
+ packet_size(ps),
+ timeout(to),
+ usb_vid(0),
+ usb_pid(0),
+ usb_device(NULL) {
+ }
+
+ device_io_hid::device_io_hid() : device_io_hid(DEFAULT_CHANNEL, DEFAULT_TAG, DEFAULT_PACKET_SIZE, DEFAULT_TIMEOUT) {
+ }
+
+ void device_io_hid::io_hid_log(int read, unsigned char* buffer, int block_len) {
+ if (hid_verbose) {
+ char strbuffer[1024];
+ hw::buffer_to_str(strbuffer, sizeof(strbuffer), (char*)buffer, block_len);
+ MDEBUG( "HID " << (read?"<":">") <<" : "<<strbuffer);
+ }
+ }
+
+ void device_io_hid::init() {
+ int r;
+ r = hid_init();
+ ASSERT_X(r>=0, "Unable to init hidapi library. Error "+std::to_string(r)+": "+safe_hid_error(this->usb_device));
+ }
+
+ void device_io_hid::connect(void *params) {
+ hid_conn_params *p = (struct hid_conn_params*)params;
+ this->connect(p->vid, p->pid, p->interface_number, p->usage_page);
+ }
+
+ hid_device_info *device_io_hid::find_device(hid_device_info *devices_list, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page) {
+ bool select_any = !interface_number && !usage_page;
+
+ MDEBUG( "Looking for " <<
+ (select_any ? "any HID Device" : "HID Device with") <<
+ (interface_number ? (" interface_number " + std::to_string(interface_number.value())) : "") <<
+ ((interface_number && usage_page) ? " or" : "") <<
+ (usage_page ? (" usage_page " + std::to_string(usage_page.value())) : ""));
+
+ hid_device_info *result = nullptr;
+ for (; devices_list != nullptr; devices_list = devices_list->next) {
+ BOOST_SCOPE_EXIT(&devices_list, &result) {
+ MDEBUG( (result == devices_list ? "SELECTED" : "SKIPPED ") <<
+ " HID Device" <<
+ " path " << safe_hid_path(devices_list) <<
+ " interface_number " << devices_list->interface_number <<
+ " usage_page " << devices_list->usage_page);
+ }
+ BOOST_SCOPE_EXIT_END
+
+ if (result != nullptr) {
+ continue;
+ }
+
+ if (select_any) {
+ result = devices_list;
+ } else if (interface_number && devices_list->interface_number == interface_number.value()) {
+ result = devices_list;
+ } else if (usage_page && devices_list->usage_page == usage_page.value()) {
+ result = devices_list;
+ }
+ }
+
+ return result;
+ }
+
+ void device_io_hid::connect(unsigned int vid, unsigned int pid, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page) {
+ hid_device_info *hwdev_info_list;
+ hid_device *hwdev;
+
+ this->disconnect();
+
+ hwdev_info_list = hid_enumerate(vid, pid);
+ ASSERT_X(hwdev_info_list, "Unable to enumerate device "+std::to_string(vid)+":"+std::to_string(vid)+ ": "+ safe_hid_error(this->usb_device));
+ hwdev = NULL;
+ if (hid_device_info *device = find_device(hwdev_info_list, interface_number, usage_page)) {
+ hwdev = hid_open_path(device->path);
+ }
+ hid_free_enumeration(hwdev_info_list);
+ ASSERT_X(hwdev, "Unable to open device "+std::to_string(pid)+":"+std::to_string(vid));
+ this->usb_vid = vid;
+ this->usb_pid = pid;
+ this->usb_device = hwdev;
+ }
+
+
+ bool device_io_hid::connected() const {
+ return this->usb_device != NULL;
+ }
+
+ int device_io_hid::exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len) {
+ unsigned char buffer[400];
+ unsigned char padding_buffer[MAX_BLOCK+1];
+ unsigned int result;
+ int hid_ret;
+ unsigned int sw_offset;
+ unsigned int remaining;
+ unsigned int offset = 0;
+
+ ASSERT_X(this->usb_device,"No device opened");
+
+ //Split command in several HID packet
+ memset(buffer, 0, sizeof(buffer));
+ result = this->wrapCommand(command, cmd_len, buffer, sizeof(buffer));
+ remaining = result;
+
+ while (remaining > 0) {
+ int block_size = (remaining > MAX_BLOCK ? MAX_BLOCK : remaining);
+ memset(padding_buffer, 0, sizeof(padding_buffer));
+ memcpy(padding_buffer+1, buffer + offset, block_size);
+ io_hid_log(0, padding_buffer, block_size+1);
+ hid_ret = hid_write(this->usb_device, padding_buffer, block_size+1);
+ ASSERT_X(hid_ret>=0, "Unable to send hidapi command. Error "+std::to_string(result)+": "+ safe_hid_error(this->usb_device));
+ offset += block_size;
+ remaining -= block_size;
+ }
+
+ //get first response
+ memset(buffer, 0, sizeof(buffer));
+ hid_ret = hid_read_timeout(this->usb_device, buffer, MAX_BLOCK, this->timeout);
+ ASSERT_X(hid_ret>=0, "Unable to read hidapi response. Error "+std::to_string(result)+": "+ safe_hid_error(this->usb_device));
+ result = (unsigned int)hid_ret;
+ io_hid_log(1, buffer, result);
+ offset = MAX_BLOCK;
+ //parse first response and get others if any
+ for (;;) {
+ result = this->unwrapReponse(buffer, offset, response, max_resp_len);
+ if (result != 0) {
+ break;
+ }
+ hid_ret = hid_read_timeout(this->usb_device, buffer + offset, MAX_BLOCK, this->timeout);
+ ASSERT_X(hid_ret>=0, "Unable to receive hidapi response. Error "+std::to_string(result)+": "+ safe_hid_error(this->usb_device));
+ result = (unsigned int)hid_ret;
+ io_hid_log(1, buffer + offset, result);
+ offset += MAX_BLOCK;
+ }
+ return result;
+ }
+
+ void device_io_hid::disconnect(void) {
+ if (this->usb_device) {
+ hid_close(this->usb_device);
+ }
+ this->usb_vid = 0;
+ this->usb_pid = 0;
+ this->usb_device = NULL;
+ }
+
+ void device_io_hid::release() {
+ /* Do not exit, as the lib context is global*/
+ //hid_exit();
+ }
+
+ unsigned int device_io_hid::wrapCommand(const unsigned char *command, size_t command_len, unsigned char *out, size_t out_len) {
+ unsigned int sequence_idx = 0;
+ unsigned int offset = 0;
+ unsigned int offset_out = 0;
+ unsigned int block_size;
+
+ ASSERT_X(this->packet_size >= 3, "Invalid Packet size: "+std::to_string(this->packet_size)) ;
+ ASSERT_X(out_len >= 7, "out_len too short: "+std::to_string(out_len));
+
+ out_len -= 7;
+ out[offset_out++] = ((this->channel >> 8) & 0xff);
+ out[offset_out++] = (this->channel & 0xff);
+ out[offset_out++] = this->tag;
+ out[offset_out++] = ((sequence_idx >> 8) & 0xff);
+ out[offset_out++] = (sequence_idx & 0xff);
+ sequence_idx++;
+ out[offset_out++] = ((command_len >> 8) & 0xff);
+ out[offset_out++] = (command_len & 0xff);
+ block_size = (command_len > this->packet_size - 7 ? this->packet_size - 7 : command_len);
+ ASSERT_X(out_len >= block_size, "out_len too short: "+std::to_string(out_len));
+ out_len -= block_size;
+ memcpy(out + offset_out, command + offset, block_size);
+ offset_out += block_size;
+ offset += block_size;
+ while (offset != command_len) {
+ ASSERT_X(out_len >= 5, "out_len too short: "+std::to_string(out_len));
+ out_len -= 5;
+ out[offset_out++] = ((this->channel >> 8) & 0xff);
+ out[offset_out++] = (this->channel & 0xff);
+ out[offset_out++] = this->tag;
+ out[offset_out++] = ((sequence_idx >> 8) & 0xff);
+ out[offset_out++] = (sequence_idx & 0xff);
+ sequence_idx++;
+ block_size = ((command_len - offset) > this->packet_size - 5 ? this->packet_size - 5 : command_len - offset);
+ ASSERT_X(out_len >= block_size, "out_len too short: "+std::to_string(out_len));
+ out_len -= block_size;
+ memcpy(out + offset_out, command + offset, block_size);
+ offset_out += block_size;
+ offset += block_size;
+ }
+ while ((offset_out % this->packet_size) != 0) {
+ ASSERT_X(out_len >= 1, "out_len too short: "+std::to_string(out_len));
+ out_len--;
+ out[offset_out++] = 0;
+ }
+ return offset_out;
+ }
+
+ /*
+ * return 0 if more data are needed
+ * >0 if response is fully available
+ */
+ unsigned int device_io_hid::unwrapReponse(const unsigned char *data, size_t data_len, unsigned char *out, size_t out_len) {
+ unsigned int sequence_idx = 0;
+ unsigned int offset = 0;
+ unsigned int offset_out = 0;
+ unsigned int response_len;
+ unsigned int block_size;
+ unsigned int val;
+
+ //end?
+ if ((data == NULL) || (data_len < 7 + 5)) {
+ return 0;
+ }
+
+ //check hid header
+ val = (data[offset]<<8) + data[offset+1];
+ offset += 2;
+ ASSERT_X(val == this->channel, "Wrong Channel");
+ val = data[offset];
+ offset++;
+ ASSERT_X(val == this->tag, "Wrong TAG");
+ val = (data[offset]<<8) + data[offset+1];
+ offset += 2;
+ ASSERT_X(val == sequence_idx, "Wrong sequence_idx");
+
+ //fetch
+ response_len = (data[offset++] << 8);
+ response_len |= data[offset++];
+ ASSERT_X(out_len >= response_len, "Out Buffer too short");
+ if (data_len < (7 + response_len)) {
+ return 0;
+ }
+ block_size = (response_len > (this->packet_size - 7) ? this->packet_size - 7 : response_len);
+ memcpy(out + offset_out, data + offset, block_size);
+ offset += block_size;
+ offset_out += block_size;
+ while (offset_out != response_len) {
+ sequence_idx++;
+ if (offset == data_len) {
+ return 0;
+ }
+ val = (data[offset]<<8) + data[offset+1];
+ offset += 2;
+ ASSERT_X(val == this->channel, "Wrong Channel");
+ val = data[offset];
+ offset++;
+ ASSERT_X(val == this->tag, "Wrong TAG");
+ val = (data[offset]<<8) + data[offset+1];
+ offset += 2;
+ ASSERT_X(val == sequence_idx, "Wrong sequence_idx");
+
+ block_size = ((response_len - offset_out) > this->packet_size - 5 ? this->packet_size - 5 : response_len - offset_out);
+ if (block_size > (data_len - offset)) {
+ return 0;
+ }
+ memcpy(out + offset_out, data + offset, block_size);
+ offset += block_size;
+ offset_out += block_size;
+ }
+ return offset_out;
+ }
+
+
+ }
+}
+
+#endif //#if defined(HAVE_HIDAPI)
diff --git a/src/device/device_io_hid.hpp b/src/device/device_io_hid.hpp
new file mode 100644
index 000000000..bb0f0a814
--- /dev/null
+++ b/src/device/device_io_hid.hpp
@@ -0,0 +1,110 @@
+// Copyright (c) 2017-2018, 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 defined(HAVE_HIDAPI)
+
+#include <boost/optional/optional.hpp>
+#include <hidapi/hidapi.h>
+#include "device_io.hpp"
+
+#pragma once
+
+namespace hw {
+ namespace io {
+
+
+
+ /** HID class base. Commands are formated as follow:
+ *
+ * |----------------------------------------------------------|
+ * | 2 bytes | 1 byte | 2 bytes | 2 bytes | len bytes |
+ * |-----------|----------|-----------|----------|------------|
+ * | channel | tag | sequence | len | payload |
+ * |----------------------------------------------------------|
+ */
+
+
+ struct hid_conn_params {
+ unsigned int vid;
+ unsigned int pid;
+ int interface_number;
+ unsigned short usage_page;
+ };
+
+
+ class device_io_hid: device_io {
+
+
+ private:
+
+
+ unsigned short channel;
+ unsigned char tag;
+ unsigned int packet_size;
+ unsigned int timeout;
+
+ unsigned int usb_vid;
+ unsigned int usb_pid;
+ hid_device *usb_device;
+
+ void io_hid_log(int read, unsigned char* buf, int buf_len);
+ void io_hid_init();
+ void io_hid_exit() ;
+ void io_hid_open(int vid, int pid, int mode);
+ void io_hid_close (void);
+
+ unsigned int wrapCommand(const unsigned char *command, size_t command_len, unsigned char *out, size_t out_len);
+ unsigned int unwrapReponse(const unsigned char *data, size_t data_len, unsigned char *out, size_t out_len);
+
+ hid_device_info *find_device(hid_device_info *devices_list, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page);
+
+ public:
+ bool hid_verbose = false;
+
+ static const unsigned short DEFAULT_CHANNEL = 0x0001;
+ static const unsigned char DEFAULT_TAG = 0x01;
+ static const unsigned int DEFAULT_PACKET_SIZE = 64;
+ static const unsigned int DEFAULT_TIMEOUT = 120000;
+
+ device_io_hid(unsigned short channel, unsigned char tag, unsigned int packet_zize, unsigned int timeout);
+ device_io_hid();
+ ~device_io_hid() {};
+
+ void init();
+ void connect(void *params);
+ void connect(unsigned int vid, unsigned int pid, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page);
+ bool connected() const;
+ int exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len);
+ void disconnect();
+ void release();
+ };
+ };
+};
+
+#endif //#if defined(HAVE_HIDAPI)
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
index 9b5ea0fd7..d879ee95a 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -48,21 +48,12 @@ namespace hw {
/* ===================================================================== */
/* === Debug ==== */
/* ===================================================================== */
- #ifdef WIN32
- static char *pcsc_stringify_error(LONG rv) {
- static __thread char out[20];
- sprintf_s(out, sizeof(out), "0x%08lX", rv);
-
- return out;
- }
- #endif
void set_apdu_verbose(bool verbose) {
apdu_verbose = verbose;
}
#define TRACKD MTRACE("hw")
- #define ASSERT_RV(rv) CHECK_AND_ASSERT_THROW_MES((rv)==SCARD_S_SUCCESS, "Fail SCard API : (" << (rv) << ") "<< pcsc_stringify_error(rv)<<" Device="<<this->id<<", hCard="<<hCard<<", hContext="<<hContext);
#define ASSERT_SW(sw,ok,msk) CHECK_AND_ASSERT_THROW_MES(((sw)&(mask))==(ok), "Wrong Device Status : SW=" << std::hex << (sw) << " (EXPECT=" << std::hex << (ok) << ", MASK=" << std::hex << (mask) << ")") ;
#define ASSERT_T0(exp) CHECK_AND_ASSERT_THROW_MES(exp, "Protocol assert failure: "#exp ) ;
#define ASSERT_X(exp,msg) CHECK_AND_ASSERT_THROW_MES(exp, msg);
@@ -185,41 +176,8 @@ namespace hw {
#define INS_GET_RESPONSE 0xc0
- void device_ledger::logCMD() {
- if (apdu_verbose) {
- char strbuffer[1024];
- snprintf(strbuffer, sizeof(strbuffer), "%.02x %.02x %.02x %.02x %.02x ",
- this->buffer_send[0],
- this->buffer_send[1],
- this->buffer_send[2],
- this->buffer_send[3],
- this->buffer_send[4]
- );
- const size_t len = strlen(strbuffer);
- buffer_to_str(strbuffer+len, sizeof(strbuffer)-len, (char*)(this->buffer_send+5), this->length_send-5);
- MDEBUG( "CMD :" << strbuffer);
- }
- }
-
- void device_ledger::logRESP() {
- if (apdu_verbose) {
- char strbuffer[1024];
- snprintf(strbuffer, sizeof(strbuffer), "%.02x%.02x ",
- this->buffer_recv[this->length_recv-2],
- this->buffer_recv[this->length_recv-1]
- );
- const size_t len = strlen(strbuffer);
- buffer_to_str(strbuffer+len, sizeof(strbuffer)-len, (char*)(this->buffer_recv), this->length_recv-2);
- MDEBUG( "RESP :" << strbuffer);
-
- }
- }
-
- /* -------------------------------------------------------------- */
- device_ledger::device_ledger() {
+ device_ledger::device_ledger(): hw_device(0x0101, 0x05, 64, 120000) {
this->id = device_id++;
- this->hCard = 0;
- this->hContext = 0;
this->reset_buffer();
this->mode = NONE;
this->has_view_key = false;
@@ -272,10 +230,39 @@ namespace hw {
MDEBUG( "Device "<<this->name << " UNLOCKed");
}
+
/* ======================================================================= */
- /* MISC */
+ /* IO */
/* ======================================================================= */
- int device_ledger::set_command_header(BYTE ins, BYTE p1, BYTE p2) {
+
+ void device_ledger::logCMD() {
+ if (apdu_verbose) {
+ char strbuffer[1024];
+ snprintf(strbuffer, sizeof(strbuffer), "%.02x %.02x %.02x %.02x %.02x ",
+ this->buffer_send[0],
+ this->buffer_send[1],
+ this->buffer_send[2],
+ this->buffer_send[3],
+ this->buffer_send[4]
+ );
+ const size_t len = strlen(strbuffer);
+ buffer_to_str(strbuffer+len, sizeof(strbuffer)-len, (char*)(this->buffer_send+5), this->length_send-5);
+ MDEBUG( "CMD : " << strbuffer);
+ }
+ }
+
+ void device_ledger::logRESP() {
+ if (apdu_verbose) {
+ char strbuffer[1024];
+ snprintf(strbuffer, sizeof(strbuffer), "%.04x ", this->sw);
+ const size_t len = strlen(strbuffer);
+ buffer_to_str(strbuffer+len, sizeof(strbuffer)-len, (char*)(this->buffer_recv), this->length_recv);
+ MDEBUG( "RESP : " << strbuffer);
+
+ }
+ }
+
+ int device_ledger::set_command_header(unsigned char ins, unsigned char p1, unsigned char p2) {
reset_buffer();
int offset = 0;
this->buffer_send[0] = 0x00;
@@ -286,7 +273,7 @@ namespace hw {
return 5;
}
- int device_ledger::set_command_header_noopt(BYTE ins, BYTE p1, BYTE p2) {
+ int device_ledger::set_command_header_noopt(unsigned char ins, unsigned char p1, unsigned char p2) {
int offset = set_command_header(ins, p1, p2);
//options
this->buffer_send[offset++] = 0;
@@ -294,7 +281,7 @@ namespace hw {
return offset;
}
- void device_ledger::send_simple(BYTE ins, BYTE p1) {
+ void device_ledger::send_simple(unsigned char ins, unsigned char p1) {
this->length_send = set_command_header_noopt(ins, p1);
this->exchange();
}
@@ -305,23 +292,17 @@ namespace hw {
}
unsigned int device_ledger::exchange(unsigned int ok, unsigned int mask) {
- LONG rv;
- unsigned int sw;
-
- ASSERT_T0(this->length_send <= BUFFER_SEND_SIZE);
logCMD();
- this->length_recv = BUFFER_RECV_SIZE;
- rv = SCardTransmit(this->hCard,
- SCARD_PCI_T0, this->buffer_send, this->length_send,
- NULL, this->buffer_recv, &this->length_recv);
- ASSERT_RV(rv);
- ASSERT_T0(this->length_recv >= 2);
- ASSERT_T0(this->length_recv <= BUFFER_RECV_SIZE);
- logRESP();
- sw = (this->buffer_recv[this->length_recv-2]<<8) | this->buffer_recv[this->length_recv-1];
- ASSERT_SW(sw,ok,msk);
- return sw;
+ this->length_recv = hw_device.exchange(this->buffer_send, this->length_send, this->buffer_recv, BUFFER_SEND_SIZE);
+ ASSERT_X(this->length_recv>=2, "Communication error, less than tow bytes received");
+
+ this->length_recv -= 2;
+ this->sw = (this->buffer_recv[length_recv]<<8) | this->buffer_recv[length_recv+1];
+ ASSERT_SW(this->sw,ok,msk);
+
+ logRESP();
+ return this->sw;
}
void device_ledger::reset_buffer() {
@@ -341,101 +322,25 @@ namespace hw {
}
const std::string device_ledger::get_name() const {
- if (this->full_name.empty() || (this->hCard == 0)) {
+ if (this->full_name.empty() || !this->connected()) {
return std::string("<disconnected:").append(this->name).append(">");
}
- return this->full_name;
+ return this->name;
}
bool device_ledger::init(void) {
#ifdef DEBUG_HWDEVICE
this->controle_device = &hw::get_device("default");
#endif
- LONG rv;
this->release();
- rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM,0,0, &this->hContext);
- ASSERT_RV(rv);
- MDEBUG( "Device "<<this->id <<" SCardContext created: hContext="<<this->hContext);
- this->hCard = 0;
- return true;
- }
-
- bool device_ledger::release() {
- this->disconnect();
- if (this->hContext) {
- SCardReleaseContext(this->hContext);
- MDEBUG( "Device "<<this->id <<" SCardContext released: hContext="<<this->hContext);
- this->hContext = 0;
- this->full_name.clear();
- }
+ hw_device.init();
+ MDEBUG( "Device "<<this->id <<" HIDUSB inited");
return true;
}
bool device_ledger::connect(void) {
- BYTE pbAtr[MAX_ATR_SIZE];
- LPSTR mszReaders;
- DWORD dwReaders;
- LONG rv;
- DWORD dwState, dwProtocol, dwAtrLen, dwReaderLen;
-
this->disconnect();
-#ifdef SCARD_AUTOALLOCATE
- dwReaders = SCARD_AUTOALLOCATE;
- rv = SCardListReaders(this->hContext, NULL, (LPSTR)&mszReaders, &dwReaders);
-#else
- dwReaders = 0;
- rv = SCardListReaders(this->hContext, NULL, NULL, &dwReaders);
- if (rv != SCARD_S_SUCCESS)
- return false;
- mszReaders = (LPSTR)calloc(dwReaders, sizeof(char));
- rv = SCardListReaders(this->hContext, NULL, mszReaders, &dwReaders);
-#endif
- if (rv == SCARD_S_SUCCESS) {
- char* p;
- const char* prefix = this->name.c_str();
-
- p = mszReaders;
- MDEBUG( "Looking for " << std::string(prefix));
- while (*p) {
- MDEBUG( "Device Found: " << std::string(p));
- if ((strncmp(prefix, p, strlen(prefix))==0)) {
- MDEBUG( "Device Match: " << std::string(p));
- if ((rv = SCardConnect(this->hContext,
- p, SCARD_SHARE_EXCLUSIVE, SCARD_PROTOCOL_T0,
- &this->hCard, &dwProtocol))!=SCARD_S_SUCCESS) {
- break;
- }
- MDEBUG( "Device "<<this->id <<" Connected: hCard="<<this->hCard);
- dwAtrLen = sizeof(pbAtr);
- if ((rv = SCardStatus(this->hCard, NULL, &dwReaderLen, &dwState, &dwProtocol, pbAtr, &dwAtrLen))!=SCARD_S_SUCCESS) {
- break;
- }
- MDEBUG( "Device "<<this->id <<" Status OK");
- rv = SCARD_S_SUCCESS ;
- this->full_name = std::string(p);
- break;
- }
- p += strlen(p) +1;
- }
- }
-
- if (rv == SCARD_S_SUCCESS && mszReaders) {
- #ifdef SCARD_AUTOALLOCATE
- SCardFreeMemory(this->hContext, mszReaders);
- #else
- free(mszReaders);
- #endif
- mszReaders = NULL;
- }
- if (rv != SCARD_S_SUCCESS) {
- if ( hCard) {
- SCardDisconnect(this->hCard, SCARD_UNPOWER_CARD);
- MDEBUG( "Device "<<this->id <<" disconnected: hCard="<<this->hCard);
- this->hCard = 0;
- }
- }
- ASSERT_RV(rv);
-
+ hw_device.connect(0x2c97, 0x0001, 0, 0xffa0);
this->reset();
#ifdef DEBUG_HWDEVICE
cryptonote::account_public_address pubkey;
@@ -445,15 +350,21 @@ namespace hw {
crypto::secret_key skey;
this->get_secret_keys(vkey,skey);
- return rv==SCARD_S_SUCCESS;
+ return true;
+ }
+
+ bool device_ledger::connected(void) const {
+ return hw_device.connected();
}
bool device_ledger::disconnect() {
- if (this->hCard) {
- SCardDisconnect(this->hCard, SCARD_UNPOWER_CARD);
- MDEBUG( "Device "<<this->id <<" disconnected: hCard="<<this->hCard);
- this->hCard = 0;
- }
+ hw_device.disconnect();
+ return true;
+ }
+
+ bool device_ledger::release() {
+ this->disconnect();
+ hw_device.release();
return true;
}
diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp
index e6c6e5b52..dde69fbfd 100644
--- a/src/device/device_ledger.hpp
+++ b/src/device/device_ledger.hpp
@@ -33,13 +33,7 @@
#include <cstddef>
#include <string>
#include "device.hpp"
-#ifdef WIN32
-#include <winscard.h>
-#define MAX_ATR_SIZE 33
-#else
-#include <PCSC/winscard.h>
-#include <PCSC/wintypes.h>
-#endif
+#include "device_io_hid.hpp"
#include <boost/thread/mutex.hpp>
#include <boost/thread/recursive_mutex.hpp>
@@ -89,22 +83,23 @@ namespace hw {
mutable boost::recursive_mutex device_locker;
mutable boost::mutex command_locker;
- //PCSC management
- std::string full_name;
- SCARDCONTEXT hContext;
- SCARDHANDLE hCard;
- DWORD length_send;
- BYTE buffer_send[BUFFER_SEND_SIZE];
- DWORD length_recv;
- BYTE buffer_recv[BUFFER_RECV_SIZE];
- unsigned int id;
+ //IO
+ hw::io::device_io_hid hw_device;
+ std::string full_name;
+ unsigned int length_send;
+ unsigned char buffer_send[BUFFER_SEND_SIZE];
+ unsigned int length_recv;
+ unsigned char buffer_recv[BUFFER_RECV_SIZE];
+ unsigned int sw;
+ unsigned int id;
void logCMD(void);
void logRESP(void);
- unsigned int exchange(unsigned int ok=0x9000, unsigned int mask=0xFFFF);
+ unsigned int exchange(unsigned int ok=0x9000, unsigned int mask=0xFFFF);
void reset_buffer(void);
- int set_command_header(BYTE ins, BYTE p1 = 0x00, BYTE p2 = 0x00);
- int set_command_header_noopt(BYTE ins, BYTE p1 = 0x00, BYTE p2 = 0x00);
- void send_simple(BYTE ins, BYTE p1 = 0x00);
+ int set_command_header(unsigned char ins, unsigned char p1 = 0x00, unsigned char p2 = 0x00);
+ int set_command_header_noopt(unsigned char ins, unsigned char p1 = 0x00, unsigned char p2 = 0x00);
+ void send_simple(unsigned char ins, unsigned char p1 = 0x00);
+
// hw running mode
device_mode mode;
@@ -127,7 +122,7 @@ namespace hw {
device_ledger(const device_ledger &device) = delete ;
device_ledger& operator=(const device_ledger &device) = delete;
- explicit operator bool() const override {return this->hContext != 0;}
+ explicit operator bool() const override {return this->connected(); }
bool reset(void);
@@ -141,8 +136,11 @@ namespace hw {
bool release() override;
bool connect(void) override;
bool disconnect() override;
+ bool connected(void) const;
+
+ bool set_mode(device_mode mode) override;
- bool set_mode(device_mode mode) override;
+ device_type get_type() const override {return device_type::LEDGER;};
/* ======================================================================= */
/* LOCKER */
diff --git a/src/device/log.cpp b/src/device/log.cpp
index 1707524fb..c9d3b551b 100644
--- a/src/device/log.cpp
+++ b/src/device/log.cpp
@@ -32,29 +32,34 @@
namespace hw {
- #ifdef WITH_DEVICE_LEDGER
- namespace ledger {
-
- #undef MONERO_DEFAULT_LOG_CATEGORY
- #define MONERO_DEFAULT_LOG_CATEGORY "device.ledger"
+ #undef MONERO_DEFAULT_LOG_CATEGORY
+ #define MONERO_DEFAULT_LOG_CATEGORY "device"
- void buffer_to_str(char *to_buff, size_t to_len, const char *buff, size_t len) {
- CHECK_AND_ASSERT_THROW_MES(to_len > (len*2), "destination buffer too short. At least" << (len*2+1) << " bytes required");
- for (size_t i=0; i<len; i++) {
- sprintf(to_buff+2*i, "%.02x", (unsigned char)buff[i]);
- }
+ void buffer_to_str(char *to_buff, size_t to_len, const char *buff, size_t len) {
+ CHECK_AND_ASSERT_THROW_MES(to_len > (len*2), "destination buffer too short. At least" << (len*2+1) << " bytes required");
+ for (size_t i=0; i<len; i++) {
+ sprintf(to_buff+2*i, "%.02x", (unsigned char)buff[i]);
}
+ }
- void log_hexbuffer(const std::string &msg, const char* buff, size_t len) {
- char logstr[1025];
- buffer_to_str(logstr, sizeof(logstr), buff, len);
- MDEBUG(msg<< ": " << logstr);
- }
+ void log_hexbuffer(const std::string &msg, const char* buff, size_t len) {
+ char logstr[1025];
+ buffer_to_str(logstr, sizeof(logstr), buff, len);
+ MDEBUG(msg<< ": " << logstr);
+ }
- void log_message(const std::string &msg, const std::string &info ) {
- MDEBUG(msg << ": " << info);
- }
+ void log_message(const std::string &msg, const std::string &info ) {
+ MDEBUG(msg << ": " << info);
+ }
+
+
+ #ifdef WITH_DEVICE_LEDGER
+ namespace ledger {
+
+ #undef MONERO_DEFAULT_LOG_CATEGORY
+ #define MONERO_DEFAULT_LOG_CATEGORY "device.ledger"
+
#ifdef DEBUG_HWDEVICE
extern crypto::secret_key dbg_viewkey;
extern crypto::secret_key dbg_spendkey;
diff --git a/src/device/log.hpp b/src/device/log.hpp
index 1d1635dc1..25a214a6c 100644
--- a/src/device/log.hpp
+++ b/src/device/log.hpp
@@ -40,12 +40,13 @@
namespace hw {
+ void buffer_to_str(char *to_buff, size_t to_len, const char *buff, size_t len) ;
+ void log_hexbuffer(const std::string &msg, const char* buff, size_t len);
+ void log_message(const std::string &msg, const std::string &info );
+
#ifdef WITH_DEVICE_LEDGER
namespace ledger {
- void buffer_to_str(char *to_buff, size_t to_len, const char *buff, size_t len) ;
- void log_hexbuffer(const std::string &msg, const char* buff, size_t len);
- void log_message(const std::string &msg, const std::string &info );
#ifdef DEBUG_HWDEVICE
#define TRACK printf("file %s:%d\n",__FILE__, __LINE__)
//#define TRACK MCDEBUG("ledger"," At file " << __FILE__ << ":" << __LINE__)
diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp
index f11f442bc..8262e86f7 100644
--- a/src/gen_multisig/gen_multisig.cpp
+++ b/src/gen_multisig/gen_multisig.cpp
@@ -104,7 +104,7 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
wallets[n]->decrypt_keys(pwd_container->password());
if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n]))
{
- tools::fail_msg_writer() << tr("Failed to verify multisig info");
+ tools::fail_msg_writer() << genms::tr("Failed to verify multisig info");
return false;
}
wallets[n]->encrypt_keys(pwd_container->password());
@@ -130,8 +130,8 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
ss << " " << name << std::endl;
}
- // finalize step if needed
- if (!extra_info[0].empty())
+ //exchange keys unless exchange_multisig_keys returns no extra info
+ while (!extra_info[0].empty())
{
std::unordered_set<crypto::public_key> pkeys;
std::vector<crypto::public_key> signers(total);
@@ -145,11 +145,7 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
}
for (size_t n = 0; n < total; ++n)
{
- if (!wallets[n]->finalize_multisig(pwd_container->password(), pkeys, signers))
- {
- tools::fail_msg_writer() << genms::tr("Error finalizing multisig");
- return false;
- }
+ extra_info[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), pkeys, signers);
}
}
@@ -167,6 +163,8 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
int main(int argc, char* argv[])
{
+ TRY_ENTRY();
+
po::options_description desc_params(wallet_args::tr("Wallet options"));
command_line::add_arg(desc_params, arg_filename_base);
command_line::add_arg(desc_params, arg_scheme);
@@ -244,15 +242,10 @@ int main(int argc, char* argv[])
return 1;
}
- if (threshold != total-1 && threshold != total)
- {
- tools::fail_msg_writer() << genms::tr("Error: unsupported scheme: only N/N and N-1/N are supported");
- return 1;
- }
bool create_address_file = command_line::get_arg(*vm, arg_create_address_file);
if (!generate_multisig(threshold, total, basename, testnet ? TESTNET : stagenet ? STAGENET : MAINNET, create_address_file))
return 1;
return 0;
- //CATCH_ENTRY_L0("main", 1);
+ CATCH_ENTRY_L0("main", 1);
}
diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp
index a0a788b7d..33d0a312f 100644
--- a/src/multisig/multisig.cpp
+++ b/src/multisig/multisig.cpp
@@ -84,6 +84,43 @@ namespace cryptonote
}
}
//-----------------------------------------------------------------
+ std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations)
+ {
+ std::vector<crypto::public_key> multisig_keys;
+ crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key);
+ for (const auto &k: derivations)
+ {
+ rct::key d = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey));
+ multisig_keys.push_back(rct::rct2pk(d));
+ }
+
+ return multisig_keys;
+ }
+ //-----------------------------------------------------------------
+ crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& multisig_keys)
+ {
+ rct::key secret_key = rct::zero();
+ for (const auto &k: multisig_keys)
+ {
+ sc_add(secret_key.bytes, secret_key.bytes, (const unsigned char*)k.data);
+ }
+
+ return rct::rct2sk(secret_key);
+ }
+ //-----------------------------------------------------------------
+ std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations)
+ {
+ std::vector<crypto::secret_key> multisig_keys;
+ multisig_keys.reserve(derivations.size());
+
+ for (const auto &k: derivations)
+ {
+ multisig_keys.emplace_back(get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(k))));
+ }
+
+ return multisig_keys;
+ }
+ //-----------------------------------------------------------------
crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys)
{
rct::key view_skey = rct::sk2rct(get_multisig_blinded_secret_key(skey));
@@ -92,7 +129,7 @@ namespace cryptonote
return rct::rct2sk(view_skey);
}
//-----------------------------------------------------------------
- crypto::public_key generate_multisig_N1_N_spend_public_key(const std::vector<crypto::public_key> &pkeys)
+ crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys)
{
rct::key spend_public_key = rct::identity();
for (const auto &pk: pkeys)
@@ -141,4 +178,9 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------
+ uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold)
+ {
+ CHECK_AND_ASSERT_THROW_MES(participants >= threshold, "participants must be greater or equal than threshold");
+ return participants - threshold + 1;
+ }
}
diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h
index f95611441..93a756812 100644
--- a/src/multisig/multisig.h
+++ b/src/multisig/multisig.h
@@ -41,9 +41,31 @@ namespace cryptonote
crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key);
void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey);
void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey);
+ /**
+ * @brief generate_multisig_derivations performs common DH key derivation.
+ * Each middle round in M/N scheme is DH exchange of public multisig keys of other participants multiplied by secret spend key of current participant.
+ * this functions does the following: new multisig key = secret spend * public multisig key
+ * @param keys - current wallet's keys
+ * @param derivations - public multisig keys of other participants
+ * @return new public multisig keys derived from previous round. This data needs to be exchange with other participants
+ */
+ std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations);
+ crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& derivations);
+ /**
+ * @brief calculate_multisig_keys. Calculates secret multisig keys from others' participants ones as follows: mi = H(Mi)
+ * @param derivations - others' participants public multisig keys.
+ * @return vector of current wallet's multisig secret keys
+ */
+ std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations);
crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys);
- crypto::public_key generate_multisig_N1_N_spend_public_key(const std::vector<crypto::public_key> &pkeys);
+ /**
+ * @brief generate_multisig_M_N_spend_public_key calculates multisig wallet's spend public key by summing all of public multisig keys
+ * @param pkeys unique public multisig keys
+ * @return multisig wallet's spend public key
+ */
+ crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys);
bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki);
void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R);
bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, cryptonote::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, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki);
+ uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold);
}
diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp
index c9ca63f43..e9d2061e8 100644
--- a/src/p2p/net_node.cpp
+++ b/src/p2p/net_node.cpp
@@ -29,6 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "common/command_line.h"
+#include "cryptonote_core/cryptonote_core.h"
#include "net_node.h"
namespace nodetool
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 74924e4f4..9390626a8 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -937,7 +937,7 @@ namespace nodetool
bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()),
epee::string_tools::num_to_string_fast(ipv4.port()),
m_config.m_net_config.connection_timeout,
- con, m_bind_ip.empty() ? "0.0.0.0" : m_bind_ip);
+ con);
if(!res)
{
@@ -1002,7 +1002,7 @@ namespace nodetool
bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()),
epee::string_tools::num_to_string_fast(ipv4.port()),
m_config.m_net_config.connection_timeout,
- con, m_bind_ip.empty() ? "0.0.0.0" : m_bind_ip);
+ con);
if (!res) {
bool is_priority = is_priority_node(na);
@@ -1617,7 +1617,7 @@ namespace nodetool
return false;
}
return true;
- }, m_bind_ip.empty() ? "0.0.0.0" : m_bind_ip);
+ });
if(!r)
{
LOG_WARNING_CC(context, "Failed to call connect_async, network error.");
diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc
index abe4ef18d..381f50872 100644
--- a/src/ringct/bulletproofs.cc
+++ b/src/ringct/bulletproofs.cc
@@ -1068,6 +1068,14 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs)
CHECK_AND_ASSERT_MES(!(x_ip == rct::zero()), false, "x_ip == 0");
PERF_TIMER_STOP(VERIFY_start);
+ // pre-multiply some points by 8
+ rct::keyV proof8_V = proof.V; for (rct::key &k: proof8_V) k = rct::scalarmult8(k);
+ rct::keyV proof8_L = proof.L; for (rct::key &k: proof8_L) k = rct::scalarmult8(k);
+ rct::keyV proof8_R = proof.R; for (rct::key &k: proof8_R) k = rct::scalarmult8(k);
+ rct::key proof8_T1 = rct::scalarmult8(proof.T1);
+ rct::key proof8_T2 = rct::scalarmult8(proof.T2);
+ rct::key proof8_S = rct::scalarmult8(proof.S);
+
PERF_TIMER_START_BP(VERIFY_line_61);
// PAPER LINE 61
sc_muladd(y0.bytes, proof.taux.bytes, weight.bytes, y0.bytes);
@@ -1090,26 +1098,22 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs)
multiexp_data.reserve(proof.V.size());
sc_sub(tmp.bytes, proof.t.bytes, tmp.bytes);
sc_muladd(y1.bytes, tmp.bytes, weight.bytes, y1.bytes);
- for (size_t j = 0; j < proof.V.size(); j++)
+ for (size_t j = 0; j < proof8_V.size(); j++)
{
- sc_mul(tmp.bytes, zpow[j+2].bytes, EIGHT.bytes);
- multiexp_data.emplace_back(tmp, proof.V[j]);
+ multiexp_data.emplace_back(zpow[j+2], proof8_V[j]);
}
rct::addKeys(Y2, Y2, rct::scalarmultKey(multiexp(multiexp_data, false), weight));
- rct::key weight8;
- sc_mul(weight8.bytes, weight.bytes, EIGHT.bytes);
- sc_mul(tmp.bytes, x.bytes, weight8.bytes);
- rct::addKeys(Y3, Y3, rct::scalarmultKey(proof.T1, tmp));
+ sc_mul(tmp.bytes, x.bytes, weight.bytes);
+ rct::addKeys(Y3, Y3, rct::scalarmultKey(proof8_T1, tmp));
rct::key xsq;
sc_mul(xsq.bytes, x.bytes, x.bytes);
- sc_mul(tmp.bytes, xsq.bytes, weight8.bytes);
- rct::addKeys(Y4, Y4, rct::scalarmultKey(proof.T2, tmp));
+ sc_mul(tmp.bytes, xsq.bytes, weight.bytes);
+ rct::addKeys(Y4, Y4, rct::scalarmultKey(proof8_T2, tmp));
PERF_TIMER_STOP(VERIFY_line_61rl_new);
PERF_TIMER_START_BP(VERIFY_line_62);
// PAPER LINE 62
- sc_mul(tmp.bytes, x.bytes, EIGHT.bytes);
- rct::addKeys(Z0, Z0, rct::scalarmultKey(rct::addKeys(rct::scalarmult8(proof.A), rct::scalarmultKey(proof.S, tmp)), weight));
+ rct::addKeys(Z0, Z0, rct::scalarmultKey(rct::addKeys(rct::scalarmult8(proof.A), rct::scalarmultKey(proof8_S, x)), weight));
PERF_TIMER_STOP(VERIFY_line_62);
// Compute the number of rounds for the inner product
@@ -1192,11 +1196,9 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs)
for (size_t i = 0; i < rounds; ++i)
{
sc_mul(tmp.bytes, w[i].bytes, w[i].bytes);
- sc_mul(tmp.bytes, tmp.bytes, EIGHT.bytes);
- multiexp_data.emplace_back(tmp, proof.L[i]);
+ multiexp_data.emplace_back(tmp, proof8_L[i]);
sc_mul(tmp.bytes, winv[i].bytes, winv[i].bytes);
- sc_mul(tmp.bytes, tmp.bytes, EIGHT.bytes);
- multiexp_data.emplace_back(tmp, proof.R[i]);
+ multiexp_data.emplace_back(tmp, proof8_R[i]);
}
rct::key acc = multiexp(multiexp_data, false);
rct::addKeys(Z2, Z2, rct::scalarmultKey(acc, weight));
diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp
index 6c3c4500e..41bbf6ca3 100644
--- a/src/ringct/rctOps.cpp
+++ b/src/ringct/rctOps.cpp
@@ -252,6 +252,25 @@ namespace rct {
return k;
}
+ rct::key addKeys(const keyV &A) {
+ if (A.empty())
+ return rct::identity();
+ ge_p3 p3, tmp;
+ CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&p3, A[0].bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__));
+ for (size_t i = 1; i < A.size(); ++i)
+ {
+ CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&tmp, A[i].bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__));
+ ge_cached p2;
+ ge_p3_to_cached(&p2, &tmp);
+ ge_p1p1 p1;
+ ge_add(&p1, &p3, &p2);
+ ge_p1p1_to_p3(&p3, &p1);
+ }
+ rct::key res;
+ ge_p3_tobytes(res.bytes, &p3);
+ return res;
+ }
+
//addKeys1
//aGB = aG + B where a is a scalar, G is the basepoint, and B is a point
void addKeys1(key &aGB, const key &a, const key & B) {
diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h
index 50645821c..60e920b3a 100644
--- a/src/ringct/rctOps.h
+++ b/src/ringct/rctOps.h
@@ -132,6 +132,7 @@ namespace rct {
//for curve points: AB = A + B
void addKeys(key &AB, const key &A, const key &B);
rct::key addKeys(const key &A, const key &B);
+ rct::key addKeys(const keyV &A);
//aGB = aG + B where a is a scalar, G is the basepoint, and B is a point
void addKeys1(key &aGB, const key &a, const key & B);
//aGbB = aG + bB where a, b are scalars, G is the basepoint and B is a point
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index fe0cd9c57..0d1789a38 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -963,18 +963,16 @@ namespace rct {
const bool bulletproof = is_rct_bulletproof(rv.type);
const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts;
- key sumOutpks = identity();
+ rct::keyV masks(rv.outPk.size());
for (size_t i = 0; i < rv.outPk.size(); i++) {
- addKeys(sumOutpks, sumOutpks, rv.outPk[i].mask);
+ masks[i] = rv.outPk[i].mask;
}
+ key sumOutpks = addKeys(masks);
DP(sumOutpks);
- key txnFeeKey = scalarmultH(d2h(rv.txnFee));
+ const key txnFeeKey = scalarmultH(d2h(rv.txnFee));
addKeys(sumOutpks, txnFeeKey, sumOutpks);
- key sumPseudoOuts = identity();
- for (size_t i = 0 ; i < pseudoOuts.size() ; i++) {
- addKeys(sumPseudoOuts, sumPseudoOuts, pseudoOuts[i]);
- }
+ key sumPseudoOuts = addKeys(pseudoOuts);
DP(sumPseudoOuts);
//check pseudoOuts vs Outs..
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index a85b94541..55ee66a79 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -208,6 +208,7 @@ namespace cryptonote
res.was_bootstrap_ever_used = m_was_bootstrap_ever_used;
}
res.database_size = m_core.get_blockchain_storage().get_db().get_database_size();
+ res.update_available = m_core.is_update_available();
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -220,6 +221,15 @@ namespace cryptonote
return ss.str();
}
//------------------------------------------------------------------------------------------------------------------------------
+ static cryptonote::blobdata get_pruned_tx_json(cryptonote::transaction &tx)
+ {
+ std::stringstream ss;
+ json_archive<true> ar(ss);
+ bool r = tx.serialize_base(ar);
+ CHECK_AND_ASSERT_MES(r, cryptonote::blobdata(), "Failed to serialize rct signatures base");
+ return ss.str();
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res)
{
PERF_TIMER(on_get_blocks);
@@ -557,7 +567,7 @@ namespace cryptonote
blobdata blob = req.prune ? get_pruned_tx_blob(tx) : t_serializable_object_to_blob(tx);
e.as_hex = string_tools::buff_to_hex_nodelimer(blob);
if (req.decode_as_json)
- e.as_json = obj_to_json_str(tx);
+ e.as_json = req.prune ? get_pruned_tx_json(tx) : obj_to_json_str(tx);
e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end();
if (e.in_pool)
{
@@ -914,6 +924,8 @@ namespace cryptonote
return r;
m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, !request_has_rpc_origin || !m_restricted);
+ for (tx_info& txi : res.transactions)
+ txi.tx_blob = epee::string_tools::buff_to_hex_nodelimer(txi.tx_blob);
res.status = CORE_RPC_STATUS_OK;
return true;
}
@@ -1080,7 +1092,7 @@ namespace cryptonote
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: failed to create block template";
- LOG_ERROR("Failed to tx pub key in coinbase extra");
+ LOG_ERROR("Failed to get tx pub key in coinbase extra");
return false;
}
res.reserved_offset = slow_memmem((void*)block_blob.data(), block_blob.size(), &tx_pub_key, sizeof(tx_pub_key));
@@ -1247,6 +1259,7 @@ namespace cryptonote
response.depth = m_core.get_current_blockchain_height() - height - 1;
response.hash = string_tools::pod_to_hex(hash);
response.difficulty = m_core.get_blockchain_storage().block_difficulty(height);
+ response.cumulative_difficulty = response.block_weight = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(height);
response.reward = get_block_reward(blk);
response.block_size = response.block_weight = m_core.get_blockchain_storage().get_db().get_block_weight(height);
response.num_txes = blk.tx_hashes.size();
@@ -1594,6 +1607,7 @@ namespace cryptonote
res.was_bootstrap_ever_used = m_was_bootstrap_ever_used;
}
res.database_size = m_core.get_blockchain_storage().get_db().get_database_size();
+ res.update_available = m_core.is_update_available();
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -2106,6 +2120,8 @@ namespace cryptonote
try
{
+ // 0 is placeholder for the whole chain
+ const uint64_t req_to_height = req.to_height ? req.to_height : (m_core.get_current_blockchain_height() - 1);
for (uint64_t amount: req.amounts)
{
static struct D
@@ -2118,7 +2134,7 @@ namespace cryptonote
} d;
boost::unique_lock<boost::mutex> lock(d.mutex);
- if (d.cached && amount == 0 && d.cached_from == req.from_height && d.cached_to == req.to_height)
+ if (d.cached && amount == 0 && d.cached_from == req.from_height && d.cached_to == req_to_height)
{
res.distributions.push_back({amount, d.cached_start_height, req.binary, d.cached_distribution, d.cached_base});
if (!req.cumulative)
@@ -2133,23 +2149,23 @@ namespace cryptonote
std::vector<uint64_t> distribution;
uint64_t start_height, base;
- if (!m_core.get_output_distribution(amount, req.from_height, req.to_height, start_height, distribution, base))
+ if (!m_core.get_output_distribution(amount, req.from_height, req_to_height, start_height, distribution, base))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Failed to get rct distribution";
return false;
}
- if (req.to_height > 0 && req.to_height >= req.from_height)
+ if (req_to_height > 0 && req_to_height >= req.from_height)
{
uint64_t offset = std::max(req.from_height, start_height);
- if (offset <= req.to_height && req.to_height - offset + 1 < distribution.size())
- distribution.resize(req.to_height - offset + 1);
+ if (offset <= req_to_height && req_to_height - offset + 1 < distribution.size())
+ distribution.resize(req_to_height - offset + 1);
}
if (amount == 0)
{
d.cached_from = req.from_height;
- d.cached_to = req.to_height;
+ d.cached_to = req_to_height;
d.cached_distribution = distribution;
d.cached_start_height = start_height;
d.cached_base = base;
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 1a84ee614..3b654d4cb 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 2
-#define CORE_RPC_VERSION_MINOR 0
+#define CORE_RPC_VERSION_MINOR 1
#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)
@@ -893,6 +893,7 @@ namespace cryptonote
uint64_t height_without_bootstrap;
bool was_bootstrap_ever_used;
uint64_t database_size;
+ bool update_available;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
@@ -926,6 +927,7 @@ namespace cryptonote
KV_SERIALIZE(height_without_bootstrap)
KV_SERIALIZE(was_bootstrap_ever_used)
KV_SERIALIZE(database_size)
+ KV_SERIALIZE(update_available)
END_KV_SERIALIZE_MAP()
};
};
@@ -1120,6 +1122,7 @@ namespace cryptonote
uint64_t depth;
std::string hash;
difficulty_type difficulty;
+ difficulty_type cumulative_difficulty;
uint64_t reward;
uint64_t block_size;
uint64_t block_weight;
@@ -1137,6 +1140,7 @@ namespace cryptonote
KV_SERIALIZE(depth)
KV_SERIALIZE(hash)
KV_SERIALIZE(difficulty)
+ KV_SERIALIZE(cumulative_difficulty)
KV_SERIALIZE(reward)
KV_SERIALIZE(block_size)
KV_SERIALIZE_OPT(block_weight, (uint64_t)0)
diff --git a/src/rpc/daemon_messages.h b/src/rpc/daemon_messages.h
index 8fff369df..be3138e3b 100644
--- a/src/rpc/daemon_messages.h
+++ b/src/rpc/daemon_messages.h
@@ -67,7 +67,7 @@ class classname \
// NOTE: when using a type with multiple template parameters,
// replace any comma in the template specifier with the macro
// above, or the preprocessor will eat the comma in a bad way.
-#define RPC_MESSAGE_MEMBER(type, name) type name;
+#define RPC_MESSAGE_MEMBER(type, name) type name = type{}
namespace cryptonote
diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h
index 5fc382a1e..2050e88e2 100644
--- a/src/serialization/serialization.h
+++ b/src/serialization/serialization.h
@@ -318,7 +318,7 @@ namespace serialization {
* \brief self explanatory
*/
template<class Stream>
- bool do_check_stream_state(Stream& s, boost::mpl::bool_<true>)
+ bool do_check_stream_state(Stream& s, boost::mpl::bool_<true>, bool noeof)
{
return s.good();
}
@@ -329,13 +329,13 @@ namespace serialization {
* \detailed Also checks to make sure that the stream is not at EOF
*/
template<class Stream>
- bool do_check_stream_state(Stream& s, boost::mpl::bool_<false>)
+ bool do_check_stream_state(Stream& s, boost::mpl::bool_<false>, bool noeof)
{
bool result = false;
if (s.good())
{
std::ios_base::iostate state = s.rdstate();
- result = EOF == s.peek();
+ result = noeof || EOF == s.peek();
s.clear(state);
}
return result;
@@ -347,9 +347,9 @@ namespace serialization {
* \brief calls detail::do_check_stream_state for ar
*/
template<class Archive>
- bool check_stream_state(Archive& ar)
+ bool check_stream_state(Archive& ar, bool noeof = false)
{
- return detail::do_check_stream_state(ar.stream(), typename Archive::is_saving());
+ return detail::do_check_stream_state(ar.stream(), typename Archive::is_saving(), noeof);
}
/*! \fn serialize
@@ -360,6 +360,17 @@ namespace serialization {
inline bool serialize(Archive &ar, T &v)
{
bool r = do_serialize(ar, v);
- return r && check_stream_state(ar);
+ return r && check_stream_state(ar, false);
+ }
+
+ /*! \fn serialize
+ *
+ * \brief serializes \a v into \a ar
+ */
+ template <class Archive, class T>
+ inline bool serialize_noeof(Archive &ar, T &v)
+ {
+ bool r = do_serialize(ar, v);
+ return r && check_stream_state(ar, true);
}
}
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 391d1f03e..18b596662 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -108,8 +108,7 @@ typedef cryptonote::simple_wallet sw;
tools::wallet_keys_unlocker unlocker(*m_wallet, pwd_container);
enum TransferType {
- TransferOriginal,
- TransferNew,
+ Transfer,
TransferLocked,
};
@@ -138,47 +137,6 @@ namespace
const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""};
-#ifdef WIN32
- // Translate from CP850 to UTF-8;
- // std::getline for a Windows console returns a string in CP437 or CP850; as simplewallet,
- // like all of Monero, is assumed to work internally with UTF-8 throughout, even on Windows
- // (although only implemented partially), a translation to UTF-8 is needed for input.
- //
- // Note that if a program is started inside the MSYS2 shell somebody already translates
- // console input to UTF-8, but it's not clear how one could detect that in order to avoid
- // double-translation; this code here thus breaks UTF-8 input within a MSYS2 shell,
- // unfortunately.
- //
- // Note also that input for passwords is NOT translated, to remain compatible with any
- // passwords containing special characters that predate this switch to UTF-8 support.
- template<typename T>
- static T cp850_to_utf8(const T &cp850_str)
- {
- boost::locale::generator gen;
- gen.locale_cache_enabled(true);
- std::locale loc = gen("en_US.CP850");
- const boost::locale::conv::method_type how = boost::locale::conv::default_method;
- T result;
- const char *begin = cp850_str.data();
- const char *end = begin + cp850_str.size();
- result.reserve(end-begin);
- typedef std::back_insert_iterator<T> inserter_type;
- inserter_type inserter(result);
- boost::locale::utf::code_point c;
- while(begin!=end) {
- c=boost::locale::utf::utf_traits<char>::template decode<char const *>(begin,end);
- if(c==boost::locale::utf::illegal || c==boost::locale::utf::incomplete) {
- if(how==boost::locale::conv::stop)
- throw boost::locale::conv::conversion_error();
- }
- else {
- boost::locale::utf::utf_traits<char>::template encode<inserter_type>(c,inserter);
- }
- }
- return result;
- }
-#endif
-
std::string input_line(const std::string& prompt)
{
#ifdef HAVE_READLINE
@@ -187,9 +145,10 @@ namespace
std::cout << prompt;
std::string buf;
+#ifdef _WIN32
+ buf = tools::input_line_win();
+#else
std::getline(std::cin, buf);
-#ifdef WIN32
- buf = cp850_to_utf8(buf);
#endif
return epee::string_tools::trim(buf);
@@ -209,10 +168,6 @@ namespace
epee::wipeable_string buf = pwd_container->password();
-#ifdef WIN32
- buf = cp850_to_utf8(buf);
-#endif
-
buf.trim();
return buf;
}
@@ -225,14 +180,14 @@ namespace
auto pwd_container = tools::password_container::prompt(verify, prompt);
if (!pwd_container)
{
- tools::fail_msg_writer() << tr("failed to read wallet password");
+ tools::fail_msg_writer() << sw::tr("failed to read wallet password");
}
return pwd_container;
}
boost::optional<tools::password_container> default_password_prompter(bool verify)
{
- return password_prompter(verify ? tr("Enter a new password for the wallet") : tr("Wallet password"), verify);
+ return password_prompter(verify ? sw::tr("Enter a new password for the wallet") : sw::tr("Wallet password"), verify);
}
inline std::string interpret_rpc_response(bool ok, const std::string& status)
@@ -310,7 +265,7 @@ namespace
}
else
{
- fail_msg_writer() << tr("invalid argument: must be either 0/1, true/false, y/n, yes/no");
+ fail_msg_writer() << sw::tr("invalid argument: must be either 0/1, true/false, y/n, yes/no");
return false;
}
}
@@ -366,18 +321,18 @@ namespace
std::string dnssec_str;
if (dnssec_valid)
{
- dnssec_str = tr("DNSSEC validation passed");
+ dnssec_str = sw::tr("DNSSEC validation passed");
}
else
{
- dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!");
+ dnssec_str = sw::tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!");
}
std::stringstream prompt;
- prompt << tr("For URL: ") << url
+ prompt << sw::tr("For URL: ") << url
<< ", " << dnssec_str << std::endl
- << tr(" Monero Address = ") << addresses[0]
+ << sw::tr(" Monero Address = ") << addresses[0]
<< std::endl
- << tr("Is this OK? (Y/n) ")
+ << sw::tr("Is this OK? (Y/n) ")
;
// prompt the user for confirmation given the dns query and dnssec status
std::string confirm_dns_ok = input_line(prompt.str());
@@ -387,7 +342,7 @@ namespace
}
if (!command_line::is_yes(confirm_dns_ok))
{
- std::cout << tr("you have cancelled the transfer request") << std::endl;
+ std::cout << sw::tr("you have cancelled the transfer request") << std::endl;
return {};
}
return addresses[0];
@@ -408,7 +363,7 @@ namespace
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;
+ fail_msg_writer() << sw::tr("failed to parse index: ") << subaddr_index_str;
subaddr_indices.clear();
return false;
}
@@ -421,7 +376,7 @@ namespace
{
auto r = tools::parse_subaddress_lookahead(str);
if (!r)
- fail_msg_writer() << tr("invalid format for subaddress lookahead; must be <major>:<minor>");
+ fail_msg_writer() << sw::tr("invalid format for subaddress lookahead; must be <major>:<minor>");
return r;
}
@@ -434,27 +389,27 @@ namespace
}
catch (const tools::error::daemon_busy&)
{
- fail_msg_writer() << tr("daemon is busy. Please try again later.");
+ fail_msg_writer() << sw::tr("daemon is busy. Please try again later.");
}
catch (const tools::error::no_connection_to_daemon&)
{
- fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running.");
+ fail_msg_writer() << sw::tr("no connection to daemon. Please make sure daemon is running.");
}
catch (const tools::error::wallet_rpc_error& e)
{
LOG_ERROR("RPC error: " << e.to_string());
- fail_msg_writer() << tr("RPC error: ") << e.what();
+ fail_msg_writer() << sw::tr("RPC error: ") << e.what();
}
catch (const tools::error::get_outs_error &e)
{
- fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what();
+ fail_msg_writer() << sw::tr("failed to get random outputs to mix: ") << e.what();
}
catch (const tools::error::not_enough_unlocked_money& e)
{
LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") %
print_money(e.available()) %
print_money(e.tx_amount()));
- fail_msg_writer() << tr("Not enough money in unlocked balance");
+ fail_msg_writer() << sw::tr("Not enough money in unlocked balance");
warn_of_possible_attack = false;
}
catch (const tools::error::not_enough_money& e)
@@ -462,7 +417,7 @@ namespace
LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") %
print_money(e.available()) %
print_money(e.tx_amount()));
- fail_msg_writer() << tr("Not enough money in unlocked balance");
+ fail_msg_writer() << sw::tr("Not enough money in unlocked balance");
warn_of_possible_attack = false;
}
catch (const tools::error::tx_not_possible& e)
@@ -472,30 +427,30 @@ namespace
print_money(e.tx_amount() + e.fee()) %
print_money(e.tx_amount()) %
print_money(e.fee()));
- fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees");
+ fail_msg_writer() << sw::tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees");
warn_of_possible_attack = false;
}
catch (const tools::error::not_enough_outs_to_mix& e)
{
auto writer = fail_msg_writer();
- writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":";
+ writer << sw::tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":";
for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs())
{
- writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second;
+ writer << "\n" << sw::tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << sw::tr("found outputs to use") << " = " << outs_for_amount.second;
}
- writer << tr("Please use sweep_unmixable.");
+ writer << sw::tr("Please use sweep_unmixable.");
}
catch (const tools::error::tx_not_constructed&)
- {
- fail_msg_writer() << tr("transaction was not constructed");
+ {
+ fail_msg_writer() << sw::tr("transaction was not constructed");
warn_of_possible_attack = false;
}
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();
+ fail_msg_writer() << (boost::format(sw::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;
+ fail_msg_writer() << sw::tr("Reason: ") << reason;
}
catch (const tools::error::tx_sum_overflow& e)
{
@@ -504,38 +459,38 @@ namespace
}
catch (const tools::error::zero_destination&)
{
- fail_msg_writer() << tr("one of destinations is zero");
+ fail_msg_writer() << sw::tr("one of destinations is zero");
warn_of_possible_attack = false;
}
catch (const tools::error::tx_too_big& e)
{
- fail_msg_writer() << tr("failed to find a suitable way to split transactions");
+ fail_msg_writer() << sw::tr("failed to find a suitable way to split transactions");
warn_of_possible_attack = false;
}
catch (const tools::error::transfer_error& e)
{
LOG_ERROR("unknown transfer error: " << e.to_string());
- fail_msg_writer() << tr("unknown transfer error: ") << e.what();
+ fail_msg_writer() << sw::tr("unknown transfer error: ") << e.what();
}
catch (const tools::error::multisig_export_needed& e)
{
LOG_ERROR("Multisig error: " << e.to_string());
- fail_msg_writer() << tr("Multisig error: ") << e.what();
+ fail_msg_writer() << sw::tr("Multisig error: ") << e.what();
warn_of_possible_attack = false;
}
catch (const tools::error::wallet_internal_error& e)
{
LOG_ERROR("internal error: " << e.to_string());
- fail_msg_writer() << tr("internal error: ") << e.what();
+ fail_msg_writer() << sw::tr("internal error: ") << e.what();
}
catch (const std::exception& e)
{
LOG_ERROR("unexpected error: " << e.what());
- fail_msg_writer() << tr("unexpected error: ") << e.what();
+ fail_msg_writer() << sw::tr("unexpected error: ") << e.what();
}
if (warn_of_possible_attack)
- fail_msg_writer() << tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a transaction immediately. Alternatively, connect to another node so the original node cannot correlate information.");
+ fail_msg_writer() << sw::tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a transaction immediately. Alternatively, connect to another node so the original node cannot correlate information.");
}
bool check_file_overwrite(const std::string &filename)
@@ -545,10 +500,10 @@ namespace
{
if (boost::ends_with(filename, ".keys"))
{
- fail_msg_writer() << boost::format(tr("File %s likely stores wallet private keys! Use a different file name.")) % filename;
+ fail_msg_writer() << boost::format(sw::tr("File %s likely stores wallet private keys! Use a different file name.")) % filename;
return false;
}
- return command_line::is_yes(input_line((boost::format(tr("File %s already exists. Are you sure to overwrite it? (Y/Yes/N/No): ")) % filename).str()));
+ return command_line::is_yes(input_line((boost::format(sw::tr("File %s already exists. Are you sure to overwrite it? (Y/Yes/N/No): ")) % filename).str()));
}
return true;
}
@@ -969,7 +924,7 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args)
{
success_msg_writer() << tr("Another step is needed");
success_msg_writer() << multisig_extra_info;
- success_msg_writer() << tr("Send this multisig info to all other participants, then use finalize_multisig <info1> [<info2>...] with others' multisig info");
+ success_msg_writer() << tr("Send this multisig info to all other participants, then use exchange_multisig_keys <info1> [<info2>...] with others' multisig info");
return true;
}
}
@@ -1043,6 +998,61 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args)
return true;
}
+bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) {
+ bool ready;
+ if (m_wallet->key_on_device())
+ {
+ fail_msg_writer() << tr("command not supported by HW wallet");
+ return true;
+ }
+ if (!m_wallet->multisig(&ready))
+ {
+ fail_msg_writer() << tr("This wallet is not multisig");
+ return true;
+ }
+ if (ready)
+ {
+ fail_msg_writer() << tr("This wallet is already finalized");
+ return true;
+ }
+
+ const auto orig_pwd_container = get_and_verify_password();
+ if(orig_pwd_container == boost::none)
+ {
+ fail_msg_writer() << tr("Your original password was incorrect.");
+ return true;
+ }
+
+ if (args.size() < 2)
+ {
+ fail_msg_writer() << tr("usage: exchange_multisig_keys <multisiginfo1> [<multisiginfo2>...]");
+ return true;
+ }
+
+ try
+ {
+ std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args);
+ if (!multisig_extra_info.empty())
+ {
+ message_writer() << tr("Another step is needed");
+ message_writer() << multisig_extra_info;
+ message_writer() << tr("Send this multisig info to all other participants, then use exchange_multisig_keys <info1> [<info2>...] with others' multisig info");
+ return true;
+ } else {
+ uint32_t threshold, total;
+ m_wallet->multisig(NULL, &threshold, &total);
+ success_msg_writer() << tr("Multisig wallet has been successfully created. Current wallet type: ") << threshold << "/" << total;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << tr("Failed to perform multisig keys exchange: ") << e.what();
+ return true;
+ }
+
+ return true;
+}
+
bool simple_wallet::export_multisig(const std::vector<std::string> &args)
{
bool ready;
@@ -1630,7 +1640,7 @@ bool simple_wallet::blackball(const std::vector<std::string> &args)
uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets;
if (args.size() == 0)
{
- fail_msg_writer() << tr("usage: blackball <amount>/<offset> | <filename> [add]");
+ fail_msg_writer() << tr("usage: mark_output_spent <amount>/<offset> | <filename> [add]");
return true;
}
@@ -1708,7 +1718,7 @@ bool simple_wallet::blackball(const std::vector<std::string> &args)
}
catch (const std::exception &e)
{
- fail_msg_writer() << tr("Failed to blackball output: ") << e.what();
+ fail_msg_writer() << tr("Failed to mark output spent: ") << e.what();
}
return true;
@@ -1719,7 +1729,7 @@ bool simple_wallet::unblackball(const std::vector<std::string> &args)
std::pair<uint64_t, uint64_t> output;
if (args.size() != 1)
{
- fail_msg_writer() << tr("usage: unblackball <amount>/<offset>");
+ fail_msg_writer() << tr("usage: mark_output_unspent <amount>/<offset>");
return true;
}
@@ -1735,7 +1745,7 @@ bool simple_wallet::unblackball(const std::vector<std::string> &args)
}
catch (const std::exception &e)
{
- fail_msg_writer() << tr("Failed to unblackball output: ") << e.what();
+ fail_msg_writer() << tr("Failed to mark output unspent: ") << e.what();
}
return true;
@@ -1746,7 +1756,7 @@ bool simple_wallet::blackballed(const std::vector<std::string> &args)
std::pair<uint64_t, uint64_t> output;
if (args.size() != 1)
{
- fail_msg_writer() << tr("usage: blackballed <amount>/<offset>");
+ fail_msg_writer() << tr("usage: is_output_spent <amount>/<offset>");
return true;
}
@@ -1759,13 +1769,13 @@ bool simple_wallet::blackballed(const std::vector<std::string> &args)
try
{
if (m_wallet->is_output_blackballed(output))
- message_writer() << tr("Blackballed: ") << output.first << "/" << output.second;
+ message_writer() << tr("Spent: ") << output.first << "/" << output.second;
else
- message_writer() << tr("not blackballed: ") << output.first << "/" << output.second;
+ message_writer() << tr("Not spent: ") << output.first << "/" << output.second;
}
catch (const std::exception &e)
{
- fail_msg_writer() << tr("Failed to unblackball output: ") << e.what();
+ fail_msg_writer() << tr("Failed to check whether output is spent: ") << e.what();
}
return true;
@@ -2297,11 +2307,7 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("bc_height",
boost::bind(&simple_wallet::show_blockchain_height, this, _1),
tr("Show the blockchain height."));
- m_cmd_binder.set_handler("transfer_original",
- boost::bind(&simple_wallet::transfer, this, _1),
- tr("transfer_original [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"),
- tr("Transfer <amount> to <address> using an older transaction building algorithm. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)"));
- m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1),
+ m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1),
tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"),
tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)"));
m_cmd_binder.set_handler("locked_transfer",
@@ -2316,15 +2322,15 @@ simple_wallet::simple_wallet()
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 [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"),
- tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used."));
+ tr("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id>]"),
+ tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. If the parameter \"outputs=<N>\" is specified and N > 0, wallet splits the transaction into N even outputs."));
m_cmd_binder.set_handler("sweep_below",
boost::bind(&simple_wallet::sweep_below, this, _1),
tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"),
tr("Send all unlocked outputs below the threshold to an address."));
m_cmd_binder.set_handler("sweep_single",
boost::bind(&simple_wallet::sweep_single, this, _1),
- tr("sweep_single [<priority>] [<ring_size>] <key_image> <address> [<payment_id>]"),
+ tr("sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id>]"),
tr("Send a single output of the given key image to an address without change."));
m_cmd_binder.set_handler("donate",
boost::bind(&simple_wallet::donate, this, _1),
@@ -2397,7 +2403,7 @@ simple_wallet::simple_wallet()
"store-tx-info <1|0>\n "
" Whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference.\n "
"default-ring-size <n>\n "
- " Set the default ring size (default and minimum is 5).\n "
+ " Set the default ring size (obsolete).\n "
"auto-refresh <1|0>\n "
" Whether to automatically synchronize new blocks from the daemon.\n "
"refresh-type <full|optimize-coinbase|no-coinbase|default>\n "
@@ -2557,6 +2563,10 @@ simple_wallet::simple_wallet()
boost::bind(&simple_wallet::finalize_multisig, this, _1),
tr("finalize_multisig <string> [<string>...]"),
tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets"));
+ m_cmd_binder.set_handler("exchange_multisig_keys",
+ boost::bind(&simple_wallet::exchange_multisig_keys, this, _1),
+ tr("exchange_multisig_keys <string> [<string>...]"),
+ tr("Performs extra multisig keys exchange rounds. Needed for arbitrary M/N multisig wallets"));
m_cmd_binder.set_handler("export_multisig_info",
boost::bind(&simple_wallet::export_multisig, this, _1),
tr("export_multisig_info <filename>"),
@@ -2589,18 +2599,18 @@ simple_wallet::simple_wallet()
boost::bind(&simple_wallet::save_known_rings, this, _1),
tr("save_known_rings"),
tr("Save known rings to the shared rings database"));
- m_cmd_binder.set_handler("blackball",
+ m_cmd_binder.set_handler("mark_output_spent",
boost::bind(&simple_wallet::blackball, this, _1),
- tr("blackball <amount>/<offset> | <filename> [add]"),
- tr("Blackball output(s) so they never get selected as fake outputs in a ring"));
- m_cmd_binder.set_handler("unblackball",
+ tr("mark_output_spent <amount>/<offset> | <filename> [add]"),
+ tr("Mark output(s) as spent so they never get selected as fake outputs in a ring"));
+ m_cmd_binder.set_handler("mark_output_unspent",
boost::bind(&simple_wallet::unblackball, this, _1),
- tr("unblackball <amount>/<offset>"),
- tr("Unblackballs an output so it may get selected as a fake output in a ring"));
- m_cmd_binder.set_handler("blackballed",
+ tr("mark_output_unspent <amount>/<offset>"),
+ tr("Marks an output as unspent so it may get selected as a fake output in a ring"));
+ m_cmd_binder.set_handler("is_output_spent",
boost::bind(&simple_wallet::blackballed, this, _1),
- tr("blackballed <amount>/<offset>"),
- tr("Checks whether an output is blackballed"));
+ tr("is_output_spent <amount>/<offset>"),
+ tr("Checks whether an output is marked as spent"));
m_cmd_binder.set_handler("version",
boost::bind(&simple_wallet::version, this, _1),
tr("version"),
@@ -3022,7 +3032,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
return false;
}
crypto::secret_key viewkey;
- if (viewkey_string.hex_to_pod(unwrap(unwrap(viewkey))))
+ if (!viewkey_string.hex_to_pod(unwrap(unwrap(viewkey))))
{
fail_msg_writer() << tr("failed to parse view key secret key");
return false;
@@ -3278,10 +3288,12 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
else if (!m_generate_from_json.empty())
{
- m_wallet_file = m_generate_from_json;
try
{
- m_wallet = tools::wallet2::make_from_json(vm, false, m_wallet_file, password_prompter);
+ auto rc = tools::wallet2::make_from_json(vm, false, m_generate_from_json, password_prompter);
+ m_wallet = std::move(rc.first);
+ password = rc.second.password();
+ m_wallet_file = m_wallet->path();
}
catch (const std::exception &e)
{
@@ -4162,10 +4174,11 @@ void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid,
if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
{
crypto::hash8 payment_id8 = crypto::null_hash8;
+ crypto::hash payment_id = crypto::null_hash;
if (get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
message_writer() <<
tr("NOTE: this transaction uses an encrypted payment ID: consider using subaddresses instead");
- else
+ else if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
message_writer(console_color_red, false) <<
tr("WARNING: this transaction uses an unencrypted payment ID: consider using subaddresses instead");
}
@@ -4203,7 +4216,7 @@ boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char
// can't ask for password from a background thread
if (!m_in_manual_refresh.load(std::memory_order_relaxed))
{
- message_writer(console_color_red, false) << tr("Password needed - use the refresh command");
+ message_writer(console_color_red, false) << boost::format(tr("Password needed (%s) - use the refresh command")) % reason;
m_cmd_binder.print_prompt();
return boost::none;
}
@@ -4944,7 +4957,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
default:
LOG_ERROR("Unknown transfer method, using default");
/* FALLTHRU */
- case TransferNew:
+ case Transfer:
ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices);
break;
}
@@ -5134,12 +5147,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer(const std::vector<std::string> &args_)
{
- return transfer_main(TransferOriginal, args_);
-}
-//----------------------------------------------------------------------------------------------------
-bool simple_wallet::transfer_new(const std::vector<std::string> &args_)
-{
- return transfer_main(TransferNew, args_);
+ return transfer_main(Transfer, args_);
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::locked_transfer(const std::vector<std::string> &args_)
@@ -5263,7 +5271,7 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st
{
auto print_usage = [below]()
{
- fail_msg_writer() << boost::format(tr("usage: %s [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]")) % (below ? "sweep_below" : "sweep_all");
+ fail_msg_writer() << boost::format(tr("usage: %s [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id>]")) % (below ? "sweep_below" : "sweep_all");
};
if (args_.size() == 0)
{
@@ -5361,6 +5369,25 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st
local_args.erase(local_args.begin() + 1);
}
+ size_t outputs = 1;
+ if (local_args.size() > 0 && local_args[0].substr(0, 8) == "outputs=")
+ {
+ if (!epee::string_tools::get_xtype_from_string(outputs, local_args[0].substr(8)))
+ {
+ fail_msg_writer() << tr("Failed to parse number of outputs");
+ return true;
+ }
+ else if (outputs < 1)
+ {
+ fail_msg_writer() << tr("Amount of outputs should be greater than 0");
+ return true;
+ }
+ else
+ {
+ local_args.erase(local_args.begin());
+ }
+ }
+
std::vector<uint8_t> extra;
bool payment_id_seen = false;
if (local_args.size() >= 2)
@@ -5445,7 +5472,7 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st
try
{
// figure out what tx will be necessary
- auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices);
+ auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, outputs, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices);
if (ptx_vector.empty())
{
@@ -5586,6 +5613,25 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
return true;
}
+ size_t outputs = 1;
+ if (local_args.size() > 0 && local_args[0].substr(0, 8) == "outputs=")
+ {
+ if (!epee::string_tools::get_xtype_from_string(outputs, local_args[0].substr(8)))
+ {
+ fail_msg_writer() << tr("Failed to parse number of outputs");
+ return true;
+ }
+ else if (outputs < 1)
+ {
+ fail_msg_writer() << tr("Amount of outputs should be greater than 0");
+ return true;
+ }
+ else
+ {
+ local_args.erase(local_args.begin());
+ }
+ }
+
std::vector<uint8_t> extra;
bool payment_id_seen = false;
if (local_args.size() == 3)
@@ -5619,7 +5665,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
if (local_args.size() != 2)
{
- fail_msg_writer() << tr("usage: sweep_single [<priority>] [<ring_size>] <key_image> <address> [<payment_id>]");
+ fail_msg_writer() << tr("usage: sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id>]");
return true;
}
@@ -5674,7 +5720,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
try
{
// figure out what tx will be necessary
- auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, priority, extra);
+ auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, outputs, fake_outs_count, 0 /* unlock_time */, priority, extra);
if (ptx_vector.empty())
{
@@ -5810,7 +5856,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_)
if (!payment_id_str.empty())
local_args.push_back(payment_id_str);
message_writer() << (boost::format(tr("Donating %s %s to The Monero Project (donate.getmonero.org or %s).")) % amount_str % cryptonote::get_unit(cryptonote::get_default_decimal_point()) % MONERO_DONATION_ADDR).str();
- transfer_new(local_args);
+ transfer(local_args);
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -6590,16 +6636,16 @@ static std::string get_human_readable_timespan(std::chrono::seconds seconds)
{
uint64_t ts = seconds.count();
if (ts < 60)
- return std::to_string(ts) + tr(" seconds");
+ return std::to_string(ts) + sw::tr(" seconds");
if (ts < 3600)
- return std::to_string((uint64_t)(ts / 60)) + tr(" minutes");
+ return std::to_string((uint64_t)(ts / 60)) + sw::tr(" minutes");
if (ts < 3600 * 24)
- return std::to_string((uint64_t)(ts / 3600)) + tr(" hours");
+ return std::to_string((uint64_t)(ts / 3600)) + sw::tr(" hours");
if (ts < 3600 * 24 * 30.5)
- return std::to_string((uint64_t)(ts / (3600 * 24))) + tr(" days");
+ return std::to_string((uint64_t)(ts / (3600 * 24))) + sw::tr(" days");
if (ts < 3600 * 24 * 365.25)
- return std::to_string((uint64_t)(ts / (3600 * 24 * 30.5))) + tr(" months");
- return tr("a long time");
+ return std::to_string((uint64_t)(ts / (3600 * 24 * 30.5))) + sw::tr(" months");
+ return sw::tr("a long time");
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
@@ -8044,6 +8090,8 @@ void simple_wallet::commit_or_save(std::vector<tools::wallet2::pending_tx>& ptx_
//----------------------------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
+ TRY_ENTRY();
+
#ifdef WIN32
// Activate UTF-8 support for Boost filesystem classes on Windows
std::locale::global(boost::locale::generator().generate(""));
@@ -8107,7 +8155,7 @@ int main(int argc, char* argv[])
if (!command.empty())
{
if (!w.process_command(command))
- fail_msg_writer() << tr("Unknown command: ") << command.front();
+ fail_msg_writer() << sw::tr("Unknown command: ") << command.front();
w.stop();
w.deinit();
}
@@ -8138,5 +8186,5 @@ int main(int argc, char* argv[])
w.deinit();
}
return 0;
- //CATCH_ENTRY_L0("main", 1);
+ CATCH_ENTRY_L0("main", 1);
}
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index d50e4ce04..39b715b73 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -152,7 +152,6 @@ namespace cryptonote
bool show_blockchain_height(const std::vector<std::string> &args);
bool transfer_main(int transfer_type, const std::vector<std::string> &args);
bool transfer(const std::vector<std::string> &args);
- bool transfer_new(const std::vector<std::string> &args);
bool locked_transfer(const std::vector<std::string> &args);
bool locked_sweep_all(const std::vector<std::string> &args);
bool sweep_main(uint64_t below, bool locked, const std::vector<std::string> &args);
@@ -211,6 +210,7 @@ namespace cryptonote
bool prepare_multisig(const std::vector<std::string>& args);
bool make_multisig(const std::vector<std::string>& args);
bool finalize_multisig(const std::vector<std::string> &args);
+ bool exchange_multisig_keys(const std::vector<std::string> &args);
bool export_multisig(const std::vector<std::string>& args);
bool import_multisig(const std::vector<std::string>& args);
bool accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs);
diff --git a/src/version.cpp.in b/src/version.cpp.in
index 55ba51f50..ff2405811 100644
--- a/src/version.cpp.in
+++ b/src/version.cpp.in
@@ -1,6 +1,6 @@
#define DEF_MONERO_VERSION_TAG "@VERSIONTAG@"
-#define DEF_MONERO_VERSION "0.12.3.0-master"
-#define DEF_MONERO_RELEASE_NAME "Lithium Luna"
+#define DEF_MONERO_VERSION "0.13.0.0-master"
+#define DEF_MONERO_RELEASE_NAME "Beryllium Bullet"
#define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG
#include "version.h"
diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp
index 8d200220d..913e3156f 100644
--- a/src/wallet/api/pending_transaction.cpp
+++ b/src/wallet/api/pending_transaction.cpp
@@ -200,7 +200,11 @@ std::string PendingTransactionImpl::multisigSignData() {
throw std::runtime_error("wallet is not multisig");
}
- auto cipher = m_wallet.m_wallet->save_multisig_tx(m_pending_tx);
+ tools::wallet2::multisig_tx_set txSet;
+ txSet.m_ptx = m_pending_tx;
+ txSet.m_signers = m_signers;
+ auto cipher = m_wallet.m_wallet->save_multisig_tx(txSet);
+
return epee::string_tools::buff_to_hex_nodelimer(cipher);
} catch (const std::exception& e) {
m_status = Status_Error;
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 463185284..1b4370c36 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -96,6 +96,9 @@ namespace {
throw runtime_error("Multisig wallet is not finalized yet");
}
}
+ void checkMultisigWalletReady(const std::unique_ptr<tools::wallet2> &wallet) {
+ return checkMultisigWalletReady(wallet.get());
+ }
void checkMultisigWalletNotReady(const tools::wallet2* wallet) {
if (!wallet) {
@@ -111,6 +114,9 @@ namespace {
throw runtime_error("Multisig wallet is already finalized");
}
}
+ void checkMultisigWalletNotReady(const std::unique_ptr<tools::wallet2> &wallet) {
+ return checkMultisigWalletNotReady(wallet.get());
+ }
}
struct Wallet2CallbackImpl : public tools::i_wallet2_callback
@@ -376,15 +382,15 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds)
, m_rebuildWalletCache(false)
, m_is_connected(false)
{
- m_wallet = new tools::wallet2(static_cast<cryptonote::network_type>(nettype), kdf_rounds, true);
- m_history = new TransactionHistoryImpl(this);
- m_wallet2Callback = new Wallet2CallbackImpl(this);
- m_wallet->callback(m_wallet2Callback);
+ m_wallet.reset(new tools::wallet2(static_cast<cryptonote::network_type>(nettype), kdf_rounds, true));
+ m_history.reset(new TransactionHistoryImpl(this));
+ m_wallet2Callback.reset(new Wallet2CallbackImpl(this));
+ m_wallet->callback(m_wallet2Callback.get());
m_refreshThreadDone = false;
m_refreshEnabled = false;
- m_addressBook = new AddressBookImpl(this);
- m_subaddress = new SubaddressImpl(this);
- m_subaddressAccount = new SubaddressAccountImpl(this);
+ m_addressBook.reset(new AddressBookImpl(this));
+ m_subaddress.reset(new SubaddressImpl(this));
+ m_subaddressAccount.reset(new SubaddressAccountImpl(this));
m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS;
@@ -399,18 +405,13 @@ WalletImpl::~WalletImpl()
{
LOG_PRINT_L1(__FUNCTION__);
+ m_wallet->callback(NULL);
// Pause refresh thread - prevents refresh from starting again
pauseRefresh();
// Close wallet - stores cache and stops ongoing refresh operation
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");
}
@@ -609,7 +610,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path,
LOG_PRINT_L1("Generated new view only wallet from keys");
}
if(has_spendkey && !has_viewkey) {
- m_wallet->generate(path, password, spendkey, true, false, false);
+ m_wallet->generate(path, password, spendkey, true, false);
setSeedLanguage(language);
LOG_PRINT_L1("Generated deterministic wallet from spend key with seed language: " + language);
}
@@ -629,7 +630,7 @@ bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &p
m_recoveringFromDevice = true;
try
{
- m_wallet->restore(path, password, device_name, false);
+ m_wallet->restore(path, password, device_name);
LOG_PRINT_L1("Generated new wallet from device: " + device_name);
}
catch (const std::exception& e) {
@@ -639,6 +640,11 @@ bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &p
return true;
}
+Wallet::Device WalletImpl::getDeviceType() const
+{
+ return static_cast<Wallet::Device>(m_wallet->get_device_type());
+}
+
bool WalletImpl::open(const std::string &path, const std::string &password)
{
clearStatus();
@@ -770,7 +776,7 @@ bool WalletImpl::setPassword(const std::string &password)
{
clearStatus();
try {
- m_wallet->rewrite(m_wallet->get_wallet_file(), password);
+ m_wallet->change_password(m_wallet->get_wallet_file(), m_password, password);
m_password = password;
} catch (const std::exception &e) {
setStatusError(e.what());
@@ -1182,6 +1188,20 @@ string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold)
return string();
}
+std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &info) {
+ try {
+ clearStatus();
+ checkMultisigWalletNotReady(m_wallet);
+
+ return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info);
+ } catch (const exception& e) {
+ LOG_ERROR("Error on exchanging multisig keys: ") << e.what();
+ setStatusError(string(tr("Failed to make multisig: ")) + e.what());
+ }
+
+ return string();
+}
+
bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) {
try {
clearStatus();
@@ -1380,7 +1400,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
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 */,
+ transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, 0 /* unlock_time */,
adjusted_priority,
extra, subaddr_account, subaddr_indices);
}
@@ -1546,22 +1566,22 @@ void WalletImpl::disposeTransaction(PendingTransaction *t)
TransactionHistory *WalletImpl::history()
{
- return m_history;
+ return m_history.get();
}
AddressBook *WalletImpl::addressBook()
{
- return m_addressBook;
+ return m_addressBook.get();
}
Subaddress *WalletImpl::subaddress()
{
- return m_subaddress;
+ return m_subaddress.get();
}
SubaddressAccount *WalletImpl::subaddressAccount()
{
- return m_subaddressAccount;
+ return m_subaddressAccount.get();
}
void WalletImpl::setListener(WalletListener *l)
@@ -2141,7 +2161,29 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &outputs, bool
bool ret = m_wallet->set_blackballed_outputs(raw_outputs, add);
if (!ret)
{
- setStatusError(tr("Failed to set blackballed outputs"));
+ setStatusError(tr("Failed to mark outputs as spent"));
+ return false;
+ }
+ return true;
+}
+
+bool WalletImpl::blackballOutput(const std::string &amount, const std::string &offset)
+{
+ uint64_t raw_amount, raw_offset;
+ if (!epee::string_tools::get_xtype_from_string(raw_amount, amount))
+ {
+ setStatusError(tr("Failed to parse output amount"));
+ return false;
+ }
+ if (!epee::string_tools::get_xtype_from_string(raw_offset, offset))
+ {
+ setStatusError(tr("Failed to parse output offset"));
+ return false;
+ }
+ bool ret = m_wallet->blackball_output(std::make_pair(raw_amount, raw_offset));
+ if (!ret)
+ {
+ setStatusError(tr("Failed to mark output as spent"));
return false;
}
return true;
@@ -2163,7 +2205,7 @@ bool WalletImpl::unblackballOutput(const std::string &amount, const std::string
bool ret = m_wallet->unblackball_output(std::make_pair(raw_amount, raw_offset));
if (!ret)
{
- setStatusError(tr("Failed to unblackball output"));
+ setStatusError(tr("Failed to mark output as unspent"));
return false;
}
return true;
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index 2cbfa30d0..6d343888b 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -79,6 +79,7 @@ public:
bool recoverFromDevice(const std::string &path,
const std::string &password,
const std::string &device_name);
+ Device getDeviceType() const override;
bool close(bool store = true);
std::string seed() const override;
std::string getSeedLanguage() const override;
@@ -136,6 +137,7 @@ public:
MultisigState multisig() const override;
std::string getMultisigInfo() const override;
std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override;
+ std::string exchangeMultisigKeys(const std::vector<std::string> &info) override;
bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override;
bool exportMultisigImages(std::string& images) override;
size_t importMultisigImages(const std::vector<std::string>& images) override;
@@ -181,7 +183,8 @@ public:
virtual std::string getDefaultDataDir() const override;
virtual bool lightWalletLogin(bool &isNewWallet) const override;
virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) override;
- virtual bool blackballOutputs(const std::vector<std::string> &pubkeys, bool add) override;
+ virtual bool blackballOutputs(const std::vector<std::string> &outputs, bool add) override;
+ virtual bool blackballOutput(const std::string &amount, const std::string &offset) override;
virtual bool unblackballOutput(const std::string &amount, const std::string &offset) override;
virtual bool getRing(const std::string &key_image, std::vector<uint64_t> &ring) const override;
virtual bool getRings(const std::string &txid, std::vector<std::pair<std::string, std::vector<uint64_t>>> &rings) const override;
@@ -214,16 +217,16 @@ private:
friend class SubaddressImpl;
friend class SubaddressAccountImpl;
- tools::wallet2 * m_wallet;
+ std::unique_ptr<tools::wallet2> m_wallet;
mutable boost::mutex m_statusMutex;
mutable int m_status;
mutable std::string m_errorString;
std::string m_password;
- TransactionHistoryImpl * m_history;
- Wallet2CallbackImpl * m_wallet2Callback;
- AddressBookImpl * m_addressBook;
- SubaddressImpl * m_subaddress;
- SubaddressAccountImpl * m_subaddressAccount;
+ std::unique_ptr<TransactionHistoryImpl> m_history;
+ std::unique_ptr<Wallet2CallbackImpl> m_wallet2Callback;
+ std::unique_ptr<AddressBookImpl> m_addressBook;
+ std::unique_ptr<SubaddressImpl> m_subaddress;
+ std::unique_ptr<SubaddressAccountImpl> m_subaddressAccount;
// multi-threaded refresh stuff
std::atomic<bool> m_refreshEnabled;
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
index 933916076..ec1a84877 100644
--- a/src/wallet/api/wallet2_api.h
+++ b/src/wallet/api/wallet2_api.h
@@ -373,6 +373,10 @@ struct WalletListener
*/
struct Wallet
{
+ enum Device {
+ Device_Software = 0,
+ Device_Ledger = 1
+ };
enum Status {
Status_Ok,
@@ -703,6 +707,12 @@ struct Wallet
*/
virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0;
/**
+ * @brief exchange_multisig_keys - provides additional key exchange round for arbitrary multisig schemes (like N-1/N, M/N)
+ * @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call
+ * @return new info string if more rounds required or an empty string if wallet creation is done
+ */
+ virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info) = 0;
+ /**
* @brief finalizeMultisig - finalizes N - 1 / N multisig wallets creation
* @param extraMultisigInfo - wallet participants' extra multisig info obtained with makeMultisig call
* @return true if success
@@ -877,7 +887,10 @@ struct Wallet
virtual bool rescanSpent() = 0;
//! blackballs a set of outputs
- virtual bool blackballOutputs(const std::vector<std::string> &pubkeys, bool add) = 0;
+ virtual bool blackballOutputs(const std::vector<std::string> &outputs, bool add) = 0;
+
+ //! blackballs an output
+ virtual bool blackballOutput(const std::string &amount, const std::string &offset) = 0;
//! unblackballs an output
virtual bool unblackballOutput(const std::string &amount, const std::string &offset) = 0;
@@ -911,6 +924,12 @@ struct Wallet
virtual bool unlockKeysFile() = 0;
//! returns true if the keys file is locked
virtual bool isKeysFileLocked() = 0;
+
+ /*!
+ * \brief Queries backing device for wallet keys
+ * \return Device they are on
+ */
+ virtual Device getDeviceType() const = 0;
};
/**
@@ -1097,6 +1116,18 @@ struct WalletManager
virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const = 0;
/*!
+ * \brief determine the key storage for the specified wallet file
+ * \param device_type (OUT) wallet backend as enumerated in Wallet::Device
+ * \param keys_file_name Keys file to verify password for
+ * \param password Password to verify
+ * \return true if password correct, else false
+ *
+ * for verification only - determines key storage hardware
+ *
+ */
+ virtual bool queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds = 1) const = 0;
+
+ /*!
* \brief findWallets - searches for the wallet files by given path name recursively
* \param path - starting point to search
* \return - list of strings with found wallets (absolute paths);
diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp
index 3851ca9cc..5b262f1b7 100644
--- a/src/wallet/api/wallet_manager.cpp
+++ b/src/wallet/api/wallet_manager.cpp
@@ -167,6 +167,14 @@ bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name,
return tools::wallet2::verify_password(keys_file_name, password, no_spend_key, hw::get_device("default"), kdf_rounds);
}
+bool WalletManagerImpl::queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds) const
+{
+ hw::device::device_type type;
+ bool r = tools::wallet2::query_device(type, keys_file_name, password, kdf_rounds);
+ device_type = static_cast<Wallet::Device>(type);
+ return r;
+}
+
std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path)
{
std::vector<std::string> result;
diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h
index 8b1c8be7f..b3c0d6c00 100644
--- a/src/wallet/api/wallet_manager.h
+++ b/src/wallet/api/wallet_manager.h
@@ -76,6 +76,7 @@ public:
virtual bool closeWallet(Wallet *wallet, bool store = true) override;
bool walletExists(const std::string &path) override;
bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const override;
+ bool queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds = 1) const override;
std::vector<std::string> findWallets(const std::string &path) override;
std::string errorString() const override;
void setDaemonAddress(const std::string &address) override;
diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp
index 21f75371b..346c052b5 100644
--- a/src/wallet/node_rpc_proxy.cpp
+++ b/src/wallet/node_rpc_proxy.cpp
@@ -69,7 +69,7 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version
m_daemon_rpc_mutex.lock();
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
- CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon");
+ CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get daemon RPC version");
m_rpc_version = resp_t.version;
@@ -95,7 +95,7 @@ boost::optional<std::string> NodeRPCProxy::get_info() const
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
- CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon");
+ CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get target blockchain height");
m_height = resp_t.height;
@@ -144,7 +144,7 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version,
req_t.version = version;
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
- CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon");
+ CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get hard fork status");
m_earliest_height[version] = resp_t.earliest_height;
@@ -171,7 +171,7 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_
req_t.grace_blocks = grace_blocks;
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
- CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon");
+ CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate");
m_dynamic_base_fee_estimate = resp_t.fee;
@@ -201,7 +201,7 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f
req_t.grace_blocks = m_dynamic_base_fee_estimate_grace_blocks;
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
- CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon");
+ CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate");
m_dynamic_base_fee_estimate = resp_t.fee;
diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp
index e9fc6866d..f562d6c06 100644
--- a/src/wallet/ringdb.cpp
+++ b/src/wallet/ringdb.cpp
@@ -398,6 +398,8 @@ bool ringdb::blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> &
epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);});
tx_active = true;
+ dbr = mdb_cursor_open(txn, dbi_blackballs, &cursor);
+ THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create cursor for blackballs table: " + std::string(mdb_strerror(dbr)));
MDB_val key, data;
for (const std::pair<uint64_t, uint64_t> &output: outputs)
@@ -410,26 +412,23 @@ bool ringdb::blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> &
switch (op)
{
case BLACKBALL_BLACKBALL:
- MDEBUG("Blackballing output " << output.first << "/" << output.second);
- dbr = mdb_put(txn, dbi_blackballs, &key, &data, MDB_APPENDDUP);
+ MDEBUG("Marking output " << output.first << "/" << output.second << " as spent");
+ dbr = mdb_cursor_put(cursor, &key, &data, MDB_APPENDDUP);
if (dbr == MDB_KEYEXIST)
dbr = 0;
break;
case BLACKBALL_UNBLACKBALL:
- MDEBUG("Unblackballing output " << output.first << "/" << output.second);
- dbr = mdb_del(txn, dbi_blackballs, &key, &data);
- if (dbr == MDB_NOTFOUND)
- dbr = 0;
+ MDEBUG("Marking output " << output.first << "/" << output.second << " as unspent");
+ dbr = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH);
+ if (dbr == 0)
+ dbr = mdb_cursor_del(cursor, 0);
break;
case BLACKBALL_QUERY:
- dbr = mdb_cursor_open(txn, dbi_blackballs, &cursor);
- THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create cursor for blackballs table: " + std::string(mdb_strerror(dbr)));
dbr = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH);
THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to lookup in blackballs table: " + std::string(mdb_strerror(dbr)));
ret = dbr != MDB_NOTFOUND;
if (dbr == MDB_NOTFOUND)
dbr = 0;
- mdb_cursor_close(cursor);
break;
case BLACKBALL_CLEAR:
break;
@@ -439,6 +438,8 @@ bool ringdb::blackball_worker(const std::vector<std::pair<uint64_t, uint64_t>> &
THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to query blackballs table: " + std::string(mdb_strerror(dbr)));
}
+ mdb_cursor_close(cursor);
+
if (op == BLACKBALL_CLEAR)
{
dbr = mdb_drop(txn, dbi_blackballs, 0);
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 6502463e2..c5618375e 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -37,6 +37,8 @@
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/range/adaptor/transformed.hpp>
#include "include_base_utils.h"
using namespace epee;
@@ -66,6 +68,7 @@ using namespace epee;
#include "memwipe.h"
#include "common/base58.h"
#include "common/dns_utils.h"
+#include "common/notify.h"
#include "ringct/rctSigs.h"
#include "ringdb.h"
@@ -113,14 +116,17 @@ using namespace cryptonote;
#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
-#define SEGREGATION_FORK_HEIGHT 1546000
-#define TESTNET_SEGREGATION_FORK_HEIGHT 1000000
-#define STAGENET_SEGREGATION_FORK_HEIGHT 1000000
+#define SEGREGATION_FORK_HEIGHT 99999999
+#define TESTNET_SEGREGATION_FORK_HEIGHT 99999999
+#define STAGENET_SEGREGATION_FORK_HEIGHT 99999999
#define SEGREGATION_FORK_VICINITY 1500 /* blocks */
#define FIRST_REFRESH_GRANULARITY 1024
+#define GAMMA_PICK_HALF_WINDOW 5
+
static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1";
+static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1";
namespace
{
@@ -132,6 +138,42 @@ namespace
dir /= ".shared-ringdb";
return dir.string();
}
+
+ std::string pack_multisignature_keys(const std::string& prefix, const std::vector<crypto::public_key>& keys, const crypto::secret_key& signer_secret_key)
+ {
+ std::string data;
+ crypto::public_key signer;
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signer_secret_key, signer), "Failed to derive public spend key");
+ data += std::string((const char *)&signer, sizeof(crypto::public_key));
+
+ for (const auto &key: keys)
+ {
+ data += std::string((const char *)&key, sizeof(crypto::public_key));
+ }
+
+ data.resize(data.size() + sizeof(crypto::signature));
+
+ crypto::hash hash;
+ crypto::cn_fast_hash(data.data(), data.size() - sizeof(crypto::signature), hash);
+ crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)];
+ crypto::generate_signature(hash, signer, signer_secret_key, signature);
+
+ return MULTISIG_EXTRA_INFO_MAGIC + tools::base58::encode(data);
+ }
+
+ std::vector<crypto::public_key> secret_keys_to_public_keys(const std::vector<crypto::secret_key>& keys)
+ {
+ std::vector<crypto::public_key> public_keys;
+ public_keys.reserve(keys.size());
+
+ std::transform(keys.begin(), keys.end(), std::back_inserter(public_keys), [] (const crypto::secret_key& k) -> crypto::public_key {
+ crypto::public_key p;
+ CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(k, p), "Failed to derive public spend key");
+ return p;
+ });
+
+ return public_keys;
+ }
}
namespace
@@ -162,6 +204,7 @@ struct options {
};
const command_line::arg_descriptor<uint64_t> kdf_rounds = {"kdf-rounds", tools::wallet2::tr("Number of rounds for the key derivation function"), 1};
const command_line::arg_descriptor<std::string> hw_device = {"hw-device", tools::wallet2::tr("HW device to use"), ""};
+ const command_line::arg_descriptor<std::string> tx_notify = { "tx-notify" , "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" , "" };
};
void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file)
@@ -256,7 +299,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
trusted_daemon = false;
if (tools::is_local_address(daemon_address))
{
- MINFO(tr("Daemon is local, assuming trusted"));
+ MINFO(tools::wallet2::tr("Daemon is local, assuming trusted"));
trusted_daemon = true;
}
}
@@ -268,6 +311,17 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir);
wallet->set_ring_database(ringdb_path.string());
wallet->device_name(device_name);
+
+ try
+ {
+ if (!command_line::is_arg_defaulted(vm, opts.tx_notify))
+ wallet->set_tx_notify(std::shared_ptr<tools::Notify>(new tools::Notify(command_line::get_arg(vm, opts.tx_notify).c_str())));
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to parse tx notify spec");
+ }
+
return wallet;
}
@@ -297,10 +351,10 @@ boost::optional<tools::password_container> get_password(const boost::program_opt
THROW_WALLET_EXCEPTION_IF(!password_prompter, tools::error::wallet_internal_error, tools::wallet2::tr("no password specified; use --prompt-for-password to prompt for a password"));
- return password_prompter(verify ? tr("Enter a new password for the wallet") : tr("Wallet password"), verify);
+ return password_prompter(verify ? tools::wallet2::tr("Enter a new password for the wallet") : tools::wallet2::tr("Wallet password"), verify);
}
-std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, bool unattended, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
+std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, bool unattended, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
{
const bool testnet = command_line::get_arg(vm, opts.testnet);
const bool stagenet = command_line::get_arg(vm, opts.stagenet);
@@ -310,6 +364,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
false. Gcc will coerce this into unique_ptr(nullptr), but clang correctly
fails. This large wrapper is for the use of that macro */
std::unique_ptr<tools::wallet2> wallet;
+ epee::wipeable_string password;
const auto do_generate = [&]() -> bool {
std::string buf;
if (!epee::file_io_utils::load_file_to_string(json_file, buf)) {
@@ -447,10 +502,12 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
if (!field_seed.empty())
{
wallet->generate(field_filename, field_password, recovery_key, recover, false, create_address_file);
+ password = field_password;
}
else if (field_viewkey.empty() && !field_spendkey.empty())
{
wallet->generate(field_filename, field_password, spendkey, recover, false, create_address_file);
+ password = field_password;
}
else
{
@@ -477,6 +534,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("Address must be specified in order to create watch-only wallet"));
}
wallet->generate(field_filename, field_password, address, viewkey, create_address_file);
+ password = field_password;
}
else
{
@@ -484,6 +542,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify spend key secret key"));
}
wallet->generate(field_filename, field_password, address, spendkey, viewkey, create_address_file);
+ password = field_password;
}
}
}
@@ -496,9 +555,9 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
if (do_generate())
{
- return wallet;
+ return {std::move(wallet), tools::password_container(password)};
}
- return nullptr;
+ return {nullptr, tools::password_container{}};
}
static void throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method)
@@ -758,6 +817,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_callback(0),
m_trusted_daemon(false),
m_nettype(nettype),
+ m_multisig_rounds_passed(0),
m_always_confirm_transfers(true),
m_print_ring_members(false),
m_store_tx_info(true),
@@ -794,7 +854,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_light_wallet_connected(false),
m_light_wallet_balance(0),
m_light_wallet_unlocked_balance(0),
- m_key_on_device(false),
+ m_key_device_type(hw::device::device_type::SOFTWARE),
m_ring_history_saved(false),
m_ringdb(),
m_last_block_reward(0),
@@ -838,9 +898,10 @@ void wallet2::init_options(boost::program_options::options_description& desc_par
command_line::add_arg(desc_params, opts.shared_ringdb_dir);
command_line::add_arg(desc_params, opts.kdf_rounds);
command_line::add_arg(desc_params, opts.hw_device);
+ command_line::add_arg(desc_params, opts.tx_notify);
}
-std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
+std::pair<std::unique_ptr<wallet2>, tools::password_container> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
{
const options opts{};
return generate_from_json(json_file, vm, unattended, opts, password_prompter);
@@ -1325,9 +1386,9 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
// (that is, the prunable stuff may or may not be included)
if (!miner_tx && !pool)
process_unconfirmed(txid, tx, height);
- std::vector<size_t> outs;
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;
+ bool notify = false;
std::vector<tx_extra_field> local_tx_extra_fields;
if (tx_cache_data.tx_extra_fields.empty())
@@ -1344,8 +1405,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
size_t pk_index = 0;
std::vector<tx_scan_info_t> tx_scan_info(tx.vout.size());
std::deque<bool> output_found(tx.vout.size(), false);
+ uint64_t total_received_1 = 0;
while (!tx.vout.empty())
{
+ std::vector<size_t> outs;
// if tx.vout is not empty, we loop through all tx pubkeys
tx_extra_pub_key pub_key_field;
@@ -1518,6 +1581,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
+ ", m_transfers.size() is " + boost::lexical_cast<std::string>(m_transfers.size()));
if (kit == m_pub_keys.end())
{
+ uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount;
if (!pool)
{
m_transfers.push_back(boost::value_initialized<transfer_details>());
@@ -1530,14 +1594,13 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_key_image = tx_scan_info[o].ki;
td.m_key_image_known = !m_watch_only && !m_multisig;
td.m_key_image_partial = m_multisig;
- td.m_amount = tx.vout[o].amount;
+ td.m_amount = 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)
+ if (tx.vout[o].amount == 0)
{
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)
@@ -1565,6 +1628,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
if (0 != m_callback)
m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index);
}
+ total_received_1 += amount;
+ notify = true;
}
else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx_scan_info[o].amount)
{
@@ -1572,6 +1637,9 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
<< " from received " << print_money(tx_scan_info[o].amount) << " output already exists with "
<< (m_transfers[kit->second].m_spent ? "spent" : "unspent") << " "
<< print_money(m_transfers[kit->second].amount()) << " in tx " << m_transfers[kit->second].m_txid << ", received output ignored");
+ THROW_WALLET_EXCEPTION_IF(tx_money_got_in_outs[tx_scan_info[o].received->index] < tx_scan_info[o].amount,
+ error::wallet_internal_error, "Unexpected values of new and old outputs");
+ tx_money_got_in_outs[tx_scan_info[o].received->index] -= tx_scan_info[o].amount;
}
else
{
@@ -1579,8 +1647,14 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
<< " from received " << print_money(tx_scan_info[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_scan_info[o].received->index] -= tx_scan_info[o].amount;
-
+ THROW_WALLET_EXCEPTION_IF(tx_money_got_in_outs[tx_scan_info[o].received->index] < tx_scan_info[o].amount,
+ error::wallet_internal_error, "Unexpected values of new and old outputs");
+ THROW_WALLET_EXCEPTION_IF(m_transfers[kit->second].amount() > tx_scan_info[o].amount,
+ error::wallet_internal_error, "Unexpected values of new and old outputs");
+ tx_money_got_in_outs[tx_scan_info[o].received->index] -= m_transfers[kit->second].amount();
+
+ uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount;
+ uint64_t extra_amount = amount - m_transfers[kit->second].amount();
if (!pool)
{
transfer_details &td = m_transfers[kit->second];
@@ -1589,14 +1663,13 @@ 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_amount = tx.vout[o].amount;
+ td.m_amount = 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)
+ if (tx.vout[o].amount == 0)
{
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)
@@ -1623,6 +1696,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
if (0 != m_callback)
m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index);
}
+ total_received_1 += extra_amount;
+ notify = true;
}
}
}
@@ -1696,10 +1771,14 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
}
// remove change sent to the spending subaddress account from the list of received funds
+ uint64_t sub_change = 0;
for (auto i = tx_money_got_in_outs.begin(); i != tx_money_got_in_outs.end();)
{
if (subaddr_account && i->first.major == *subaddr_account)
+ {
+ sub_change += i->second;
i = tx_money_got_in_outs.erase(i);
+ }
else
++i;
}
@@ -1744,6 +1823,20 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
}
}
+ uint64_t total_received_2 = sub_change;
+ for (const auto& i : tx_money_got_in_outs)
+ total_received_2 += i.second;
+ if (total_received_1 != total_received_2)
+ {
+ const el::Level level = el::Level::Warning;
+ MCLOG_RED(level, "global", "**********************************************************************");
+ MCLOG_RED(level, "global", "Consistency failure in amounts received");
+ MCLOG_RED(level, "global", "Check transaction " << txid);
+ MCLOG_RED(level, "global", "**********************************************************************");
+ exit(1);
+ return;
+ }
+
for (const auto& i : tx_money_got_in_outs)
{
payment_details payment;
@@ -1765,6 +1858,13 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount);
}
}
+
+ if (notify)
+ {
+ std::shared_ptr<tools::Notify> tx_notify = m_tx_notify;
+ if (tx_notify)
+ tx_notify->notify(epee::string_tools::pod_to_hex(txid).c_str());
+ }
}
//----------------------------------------------------------------------------------------------------
void wallet2::process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height)
@@ -2860,6 +2960,7 @@ bool wallet2::clear()
m_address_book.clear();
m_subaddresses.clear();
m_subaddress_labels.clear();
+ m_multisig_rounds_passed = 0;
return true;
}
@@ -2874,6 +2975,7 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
{
std::string account_data;
std::string multisig_signers;
+ std::string multisig_derivations;
cryptonote::account_base account = m_account;
crypto::chacha_key key;
@@ -2908,7 +3010,7 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
rapidjson::Value value2(rapidjson::kNumberType);
- value2.SetInt(m_key_on_device?1:0);
+ value2.SetInt(m_key_device_type);
json.AddMember("key_on_device", value2, json.GetAllocator());
value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ??
@@ -2926,6 +3028,14 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet multisig signers");
value.SetString(multisig_signers.c_str(), multisig_signers.length());
json.AddMember("multisig_signers", value, json.GetAllocator());
+
+ r = ::serialization::dump_binary(m_multisig_derivations, multisig_derivations);
+ CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet multisig derivations");
+ value.SetString(multisig_derivations.c_str(), multisig_derivations.length());
+ json.AddMember("multisig_derivations", value, json.GetAllocator());
+
+ value2.SetUint(m_multisig_rounds_passed);
+ json.AddMember("multisig_rounds_passed", value2, json.GetAllocator());
}
value2.SetInt(m_always_confirm_transfers ? 1 :0);
@@ -3098,6 +3208,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_multisig = false;
m_multisig_threshold = 0;
m_multisig_signers.clear();
+ m_multisig_rounds_passed = 0;
+ m_multisig_derivations.clear();
m_always_confirm_transfers = false;
m_print_ring_members = false;
m_default_mixin = 0;
@@ -3121,7 +3233,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR;
m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR;
m_device_name = "";
- m_key_on_device = false;
+ m_key_device_type = hw::device::device_type::SOFTWARE;
encrypted_secret_keys = false;
}
else if(json.IsObject())
@@ -3141,8 +3253,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
if (json.HasMember("key_on_device"))
{
- GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, false);
- m_key_on_device = field_key_on_device;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, hw::device::device_type::SOFTWARE);
+ m_key_device_type = static_cast<hw::device::device_type>(field_key_on_device);
}
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_language, std::string, String, false, std::string());
@@ -3156,6 +3268,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_multisig = field_multisig;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_threshold, unsigned int, Uint, m_multisig, 0);
m_multisig_threshold = field_multisig_threshold;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_rounds_passed, unsigned int, Uint, false, 0);
+ m_multisig_rounds_passed = field_multisig_rounds_passed;
if (m_multisig)
{
if (!json.HasMember("multisig_signers"))
@@ -3176,6 +3290,24 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
LOG_ERROR("Field multisig_signers found in JSON, but failed to parse");
return false;
}
+
+ //previous version of multisig does not have this field
+ if (json.HasMember("multisig_derivations"))
+ {
+ if (!json["multisig_derivations"].IsString())
+ {
+ LOG_ERROR("Field multisig_derivations found in JSON, but not String");
+ return false;
+ }
+ const char *field_multisig_derivations = json["multisig_derivations"].GetString();
+ std::string multisig_derivations = std::string(field_multisig_derivations, field_multisig_derivations + json["multisig_derivations"].GetStringLength());
+ r = ::serialization::parse_binary(multisig_derivations, m_multisig_derivations);
+ if (!r)
+ {
+ LOG_ERROR("Field multisig_derivations found in JSON, but failed to parse");
+ return false;
+ }
+ }
}
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true);
m_always_confirm_transfers = field_always_confirm_transfers;
@@ -3257,9 +3389,16 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
encrypted_secret_keys = field_encrypted_secret_keys;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_name, std::string, String, false, std::string());
- if (m_device_name.empty() && field_device_name_found)
+ if (m_device_name.empty())
{
- m_device_name = field_device_name;
+ if (field_device_name_found)
+ {
+ m_device_name = field_device_name;
+ }
+ else
+ {
+ m_device_name = m_key_device_type == hw::device::device_type::LEDGER ? "Ledger" : "default";
+ }
}
}
else
@@ -3269,7 +3408,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
}
r = epee::serialization::load_t_from_binary(m_account, account_data);
- if (r && m_key_on_device) {
+ THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
+ if (m_key_device_type == hw::device::device_type::LEDGER) {
LOG_PRINT_L0("Account on device. Initing device...");
hw::device &hwdev = hw::get_device(m_device_name);
hwdev.set_name(m_device_name);
@@ -3277,6 +3417,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
hwdev.connect();
m_account.set_device(hwdev);
LOG_PRINT_L0("Device inited...");
+ } else if (key_on_device()) {
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "hardware device not supported");
}
if (r)
@@ -3445,6 +3587,60 @@ void wallet2::create_keys_file(const std::string &wallet_, bool watch_only, cons
/*!
+ * \brief determine the key storage for the specified wallet file
+ * \param device_type (OUT) wallet backend as enumerated in hw::device::device_type
+ * \param keys_file_name Keys file to verify password for
+ * \param password Password to verify
+ * \return true if password correct, else false
+ *
+ * for verification only - determines key storage hardware
+ *
+ */
+bool wallet2::query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds)
+{
+ rapidjson::Document json;
+ wallet2::keys_file_data keys_file_data;
+ std::string buf;
+ bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf);
+ THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name);
+
+ // Decrypt the contents
+ r = ::serialization::parse_binary(buf, keys_file_data);
+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"');
+ crypto::chacha_key key;
+ crypto::generate_chacha_key(password.data(), password.size(), key, kdf_rounds);
+ std::string account_data;
+ account_data.resize(keys_file_data.account_data.size());
+ crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
+ if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject())
+ crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]);
+
+ device_type = hw::device::device_type::SOFTWARE;
+ // The contents should be JSON if the wallet follows the new format.
+ if (json.Parse(account_data.c_str()).HasParseError())
+ {
+ // old format before JSON wallet key file format
+ }
+ else
+ {
+ account_data = std::string(json["key_data"].GetString(), json["key_data"].GetString() +
+ json["key_data"].GetStringLength());
+
+ if (json.HasMember("key_on_device"))
+ {
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, hw::device::device_type::SOFTWARE);
+ device_type = static_cast<hw::device::device_type>(field_key_on_device);
+ }
+ }
+
+ cryptonote::account_base account_data_check;
+
+ r = epee::serialization::load_t_from_binary(account_data_check, account_data);
+ if (!r) return false;
+ return true;
+}
+
+/*!
* \brief Generates a wallet or restores one.
* \param wallet_ Name of wallet file
* \param password Password of wallet file
@@ -3518,7 +3714,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
m_multisig = true;
m_multisig_threshold = threshold;
m_multisig_signers = multisig_signers;
- m_key_on_device = false;
+ m_key_device_type = hw::device::device_type::SOFTWARE;
setup_keys(password);
create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file);
@@ -3558,7 +3754,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip
m_multisig = false;
m_multisig_threshold = 0;
m_multisig_signers.clear();
- m_key_on_device = false;
+ m_key_device_type = hw::device::device_type::SOFTWARE;
setup_keys(password);
// calculate a starting refresh height
@@ -3646,7 +3842,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
m_multisig = false;
m_multisig_threshold = 0;
m_multisig_signers.clear();
- m_key_on_device = false;
+ m_key_device_type = hw::device::device_type::SOFTWARE;
setup_keys(password);
create_keys_file(wallet_, true, password, m_nettype != MAINNET || create_address_file);
@@ -3686,7 +3882,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
m_multisig = false;
m_multisig_threshold = 0;
m_multisig_signers.clear();
- m_key_on_device = false;
+ m_key_device_type = hw::device::device_type::SOFTWARE;
setup_keys(password);
create_keys_file(wallet_, false, password, create_address_file);
@@ -3713,12 +3909,12 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file);
THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);
}
- m_key_on_device = true;
auto &hwdev = hw::get_device(device_name);
hwdev.set_name(device_name);
m_account.create_from_device(hwdev);
+ m_key_device_type = m_account.get_device().get_type();
m_account_public_address = m_account.get_keys().m_account_address;
m_watch_only = false;
m_multisig = false;
@@ -3748,12 +3944,12 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys");
CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes");
CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold");
- CHECK_AND_ASSERT_THROW_MES(threshold == spend_keys.size() || threshold == spend_keys.size() + 1, "Unsupported threshold case");
std::string extra_multisig_info;
- crypto::hash hash;
-
- clear();
+ std::vector<crypto::secret_key> multisig_keys;
+ rct::key spend_pkey = rct::identity();
+ rct::key spend_skey;
+ std::vector<crypto::public_key> multisig_signers;
// decrypt keys
epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
@@ -3766,43 +3962,78 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); });
}
- MINFO("Creating spend key...");
- std::vector<crypto::secret_key> multisig_keys;
- rct::key spend_pkey, spend_skey;
+ // In common multisig scheme there are 4 types of key exchange rounds:
+ // 1. First round is exchange of view secret keys and public spend keys.
+ // 2. Middle round is exchange of derivations: Ki = b * Mj, where b - spend secret key,
+ // M - public multisig key (in first round it equals to public spend key), K - new public multisig key.
+ // 3. Secret spend establishment round sets your secret multisig keys as follows: kl = H(Ml), where M - is *your* public multisig key,
+ // k - secret multisig key used to sign transactions. k and M are sets of keys, of course.
+ // And secret spend key as the sum of all participant's secret multisig keys
+ // 4. Last round establishes multisig wallet's public spend key. Participants exchange their public multisig keys
+ // and calculate common spend public key as sum of all unique participants' public multisig keys.
+ // Note that N/N scheme has only first round. N-1/N has 2 rounds: first and last. Common M/N has all 4 rounds.
+
+ // IMPORTANT: wallet's public spend key is not equal to secret_spend_key * G!
+ // Wallet's public spend key is the sum of unique public multisig keys of all participants.
+ // secret_spend_key * G = public signer key
+
if (threshold == spend_keys.size() + 1)
{
+ // In N / N case we only need to do one round and calculate secret multisig keys and new secret spend key
+ MINFO("Creating spend key...");
+
+ // Calculates all multisig keys and spend key
cryptonote::generate_multisig_N_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey);
+
+ // Our signer key is b * G, where b is secret spend key.
+ multisig_signers = spend_keys;
+ multisig_signers.push_back(get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key));
}
- else if (threshold == spend_keys.size())
+ else
{
- cryptonote::generate_multisig_N1_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey);
+ // We just got public spend keys of all participants and deriving multisig keys (set of Mi = b * Bi).
+ // note that derivations are public keys as DH exchange suppose it to be
+ auto derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), spend_keys);
- // We need an extra step, so we package all the composite public keys
- // we know about, and make a signed string out of them
- std::string data;
- crypto::public_key signer;
- CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(rct::rct2sk(spend_skey), signer), "Failed to derive public spend key");
- data += std::string((const char *)&signer, sizeof(crypto::public_key));
+ spend_pkey = rct::identity();
+ multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey);
- for (const auto &msk: multisig_keys)
+ if (threshold == spend_keys.size())
{
- rct::key pmsk = rct::scalarmultBase(rct::sk2rct(msk));
- data += std::string((const char *)&pmsk, sizeof(crypto::public_key));
+ // N - 1 / N case
+
+ // We need an extra step, so we package all the composite public keys
+ // we know about, and make a signed string out of them
+ MINFO("Creating spend key...");
+
+ // Calculating set of our secret multisig keys as follows: mi = H(Mi),
+ // where mi - secret multisig key, Mi - others' participants public multisig key
+ multisig_keys = cryptonote::calculate_multisig_keys(derivations);
+
+ // calculating current participant's spend secret key as sum of all secret multisig keys for current participant.
+ // IMPORTANT: participant's secret spend key is not an entire wallet's secret spend!
+ // Entire wallet's secret spend is sum of all unique secret multisig keys
+ // among all of participants and is not held by anyone!
+ spend_skey = rct::sk2rct(cryptonote::calculate_multisig_signer_key(multisig_keys));
+
+ // Preparing data for the last round to calculate common public spend key. The data contains public multisig keys.
+ extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), rct::rct2sk(spend_skey));
}
+ else
+ {
+ // M / N case
+ MINFO("Preparing keys for next exchange round...");
- data.resize(data.size() + sizeof(crypto::signature));
- crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash);
- crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)];
- crypto::generate_signature(hash, signer, rct::rct2sk(spend_skey), signature);
+ // Preparing data for middle round - packing new public multisig keys to exchage with others.
+ extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, derivations, m_account.get_keys().m_spend_secret_key);
+ spend_skey = rct::sk2rct(m_account.get_keys().m_spend_secret_key);
- extra_multisig_info = std::string("MultisigxV1") + tools::base58::encode(data);
- }
- else
- {
- CHECK_AND_ASSERT_THROW_MES(false, "Unsupported threshold case");
+ // Need to store middle keys to be able to proceed in case of wallet shutdown.
+ m_multisig_derivations = derivations;
+ }
}
- // the multisig view key is shared by all, make one all can derive
+ clear();
MINFO("Creating view key...");
crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(get_account().get_keys().m_view_secret_key, view_keys);
@@ -3814,18 +4045,10 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
m_account_public_address = m_account.get_keys().m_account_address;
m_watch_only = false;
m_multisig = true;
+ m_key_device_type = hw::device::device_type::SOFTWARE;
m_multisig_threshold = threshold;
- m_key_on_device = false;
-
- if (threshold == spend_keys.size() + 1)
- {
- m_multisig_signers = spend_keys;
- m_multisig_signers.push_back(get_multisig_signer_public_key());
- }
- else
- {
- m_multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey);
- }
+ m_multisig_signers = multisig_signers;
+ ++m_multisig_rounds_passed;
// re-encrypt keys
keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
@@ -3840,13 +4063,147 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
return extra_multisig_info;
}
-std::string wallet2::make_multisig(const epee::wipeable_string &password,
- const std::vector<std::string> &info,
- uint32_t threshold)
+std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password,
+ const std::vector<std::string> &info)
+{
+ THROW_WALLET_EXCEPTION_IF(info.empty(),
+ error::wallet_internal_error, "Empty multisig info");
+
+ if (info[0].substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC)
+ {
+ THROW_WALLET_EXCEPTION_IF(false,
+ error::wallet_internal_error, "Unsupported info string");
+ }
+
+ std::vector<crypto::public_key> signers;
+ std::unordered_set<crypto::public_key> pkeys;
+
+ THROW_WALLET_EXCEPTION_IF(!unpack_extra_multisig_info(info, signers, pkeys),
+ error::wallet_internal_error, "Bad extra multisig info");
+
+ return exchange_multisig_keys(password, pkeys, signers);
+}
+
+std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password,
+ std::unordered_set<crypto::public_key> derivations,
+ std::vector<crypto::public_key> signers)
+{
+ CHECK_AND_ASSERT_THROW_MES(!derivations.empty(), "empty pkeys");
+ CHECK_AND_ASSERT_THROW_MES(!signers.empty(), "empty signers");
+
+ bool ready = false;
+ CHECK_AND_ASSERT_THROW_MES(multisig(&ready), "The wallet is not multisig");
+ CHECK_AND_ASSERT_THROW_MES(!ready, "Multisig wallet creation process has already been finished");
+
+ // keys are decrypted
+ epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
+ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
+ {
+ crypto::chacha_key chacha_key;
+ crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
+ m_account.encrypt_viewkey(chacha_key);
+ m_account.decrypt_keys(chacha_key);
+ keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); });
+ }
+
+ if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 1)
+ {
+ // the last round is passed and we have to calculate spend public key
+ // add ours if not included
+ crypto::public_key local_signer = get_multisig_signer_public_key();
+
+ if (std::find(signers.begin(), signers.end(), local_signer) == signers.end())
+ {
+ signers.push_back(local_signer);
+ for (const auto &msk: get_account().get_multisig_keys())
+ {
+ derivations.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk))));
+ }
+ }
+
+ CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size");
+
+ // Summing all of unique public multisig keys to calculate common public spend key
+ crypto::public_key spend_public_key = cryptonote::generate_multisig_M_N_spend_public_key(std::vector<crypto::public_key>(derivations.begin(), derivations.end()));
+ m_account_public_address.m_spend_public_key = spend_public_key;
+ m_account.finalize_multisig(spend_public_key);
+
+ m_multisig_signers = signers;
+ std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)); });
+
+ ++m_multisig_rounds_passed;
+ m_multisig_derivations.clear();
+
+ // keys are encrypted again
+ keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
+
+ if (!m_wallet_file.empty())
+ {
+ bool r = store_keys(m_keys_file, password, false);
+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
+
+ if (boost::filesystem::exists(m_wallet_file + ".address.txt"))
+ {
+ r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype));
+ if(!r) MERROR("String with address text not saved");
+ }
+ }
+
+ m_subaddresses.clear();
+ m_subaddress_labels.clear();
+ add_subaddress_account(tr("Primary account"));
+
+ if (!m_wallet_file.empty())
+ store();
+
+ return {};
+ }
+
+ // Below are either middle or secret spend key establishment rounds
+
+ for (const auto& key: m_multisig_derivations)
+ derivations.erase(key);
+
+ // Deriving multisig keys (set of Mi = b * Bi) according to DH from other participants' multisig keys.
+ auto new_derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), std::vector<crypto::public_key>(derivations.begin(), derivations.end()));
+
+ std::string extra_multisig_info;
+ if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 2) // next round is last
+ {
+ // Next round is last therefore we are performing secret spend establishment round as described above.
+ MINFO("Creating spend key...");
+
+ // Calculating our secret multisig keys by hashing our public multisig keys.
+ auto multisig_keys = cryptonote::calculate_multisig_keys(std::vector<crypto::public_key>(new_derivations.begin(), new_derivations.end()));
+ // And summing it to get personal secret spend key
+ crypto::secret_key spend_skey = cryptonote::calculate_multisig_signer_key(multisig_keys);
+
+ m_account.make_multisig(m_account.get_keys().m_view_secret_key, spend_skey, rct::rct2pk(rct::identity()), multisig_keys);
+
+ // Packing public multisig keys to exchange with others and calculate common public spend key in the last round
+ extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), spend_skey);
+ }
+ else
+ {
+ // This is just middle round
+ MINFO("Preparing keys for next exchange round...");
+ extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, new_derivations, m_account.get_keys().m_spend_secret_key);
+ m_multisig_derivations = new_derivations;
+ }
+
+ ++m_multisig_rounds_passed;
+
+ create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
+ return extra_multisig_info;
+}
+
+void wallet2::unpack_multisig_info(const std::vector<std::string>& info,
+ std::vector<crypto::public_key> &public_keys,
+ std::vector<crypto::secret_key> &secret_keys) const
{
// parse all multisig info
- std::vector<crypto::secret_key> secret_keys(info.size());
- std::vector<crypto::public_key> public_keys(info.size());
+ public_keys.resize(info.size());
+ secret_keys.resize(info.size());
for (size_t i = 0; i < info.size(); ++i)
{
THROW_WALLET_EXCEPTION_IF(!verify_multisig_info(info[i], secret_keys[i], public_keys[i]),
@@ -3890,75 +4247,51 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
"Found local spend public key, but not local view secret key - something very weird");
}
}
+}
+std::string wallet2::make_multisig(const epee::wipeable_string &password,
+ const std::vector<std::string> &info,
+ uint32_t threshold)
+{
+ std::vector<crypto::secret_key> secret_keys(info.size());
+ std::vector<crypto::public_key> public_keys(info.size());
+ unpack_multisig_info(info, public_keys, secret_keys);
return make_multisig(password, secret_keys, public_keys, threshold);
}
bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unordered_set<crypto::public_key> pkeys, std::vector<crypto::public_key> signers)
{
- CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys");
-
- // keys are decrypted
- epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
- if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
- {
- crypto::chacha_key chacha_key;
- crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
- m_account.encrypt_viewkey(chacha_key);
- m_account.decrypt_keys(chacha_key);
- keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); });
- }
+ exchange_multisig_keys(password, pkeys, signers);
+ return true;
+}
- // add ours if not included
- crypto::public_key local_signer;
- CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, local_signer),
- "Failed to derive public spend key");
- if (std::find(signers.begin(), signers.end(), local_signer) == signers.end())
+bool wallet2::unpack_extra_multisig_info(const std::vector<std::string>& info,
+ std::vector<crypto::public_key> &signers,
+ std::unordered_set<crypto::public_key> &pkeys) const
+{
+ // parse all multisig info
+ signers.resize(info.size(), crypto::null_pkey);
+ for (size_t i = 0; i < info.size(); ++i)
{
- signers.push_back(local_signer);
- for (const auto &msk: get_account().get_multisig_keys())
- {
- pkeys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk))));
- }
+ if (!verify_extra_multisig_info(info[i], pkeys, signers[i]))
+ {
+ return false;
+ }
}
- CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size");
-
- crypto::public_key spend_public_key = cryptonote::generate_multisig_N1_N_spend_public_key(std::vector<crypto::public_key>(pkeys.begin(), pkeys.end()));
- m_account_public_address.m_spend_public_key = spend_public_key;
- m_account.finalize_multisig(spend_public_key);
-
- m_multisig_signers = signers;
- std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)); });
-
- // keys are encrypted again
- keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
-
- create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
-
- m_subaddresses.clear();
- m_subaddress_labels.clear();
- add_subaddress_account(tr("Primary account"));
-
- if (!m_wallet_file.empty())
- store();
-
return true;
}
bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info)
{
- // parse all multisig info
std::unordered_set<crypto::public_key> public_keys;
- std::vector<crypto::public_key> signers(info.size(), crypto::null_pkey);
- for (size_t i = 0; i < info.size(); ++i)
+ std::vector<crypto::public_key> signers;
+ if (!unpack_extra_multisig_info(info, signers, public_keys))
{
- if (!verify_extra_multisig_info(info[i], public_keys, signers[i]))
- {
- MERROR("Bad multisig info");
- return false;
- }
+ MERROR("Bad multisig info");
+ return false;
}
+
return finalize_multisig(password, public_keys, signers);
}
@@ -4021,14 +4354,13 @@ bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &
bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer)
{
- const size_t header_len = strlen("MultisigxV1");
- if (data.size() < header_len || data.substr(0, header_len) != "MultisigxV1")
+ if (data.size() < MULTISIG_EXTRA_INFO_MAGIC.size() || data.substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC)
{
MERROR("Multisig info header check error");
return false;
}
std::string decoded;
- if (!tools::base58::decode(data.substr(header_len), decoded))
+ if (!tools::base58::decode(data.substr(MULTISIG_EXTRA_INFO_MAGIC.size()), decoded))
{
MERROR("Multisig info decoding error");
return false;
@@ -5236,10 +5568,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
rct::RangeProofType range_proof_type = rct::RangeProofBorromean;
if (sd.use_bulletproofs)
{
- range_proof_type = rct::RangeProofBulletproof;
- for (const rct::Bulletproof &proof: ptx.tx.rct_signatures.p.bulletproofs)
- if (proof.V.size() > 1)
- range_proof_type = rct::RangeProofPaddedBulletproof;
+ range_proof_type = rct::RangeProofPaddedBulletproof;
}
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
@@ -6468,10 +6797,29 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
error::get_output_distribution, "Decreasing offsets in rct distribution: " +
std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset]) + ", " +
std::to_string(block_offset + 1) + ": " + std::to_string(rct_offsets[block_offset + 1]));
- uint64_t n_rct = rct_offsets[block_offset + 1] - rct_offsets[block_offset];
+ uint64_t first_block_offset = block_offset, last_block_offset = block_offset;
+ for (size_t half_window = 0; half_window < GAMMA_PICK_HALF_WINDOW; ++half_window)
+ {
+ // end when we have a non empty block
+ uint64_t cum0 = first_block_offset > 0 ? rct_offsets[first_block_offset] - rct_offsets[first_block_offset - 1] : rct_offsets[0];
+ if (cum0 > 1)
+ break;
+ uint64_t cum1 = last_block_offset > 0 ? rct_offsets[last_block_offset] - rct_offsets[last_block_offset - 1] : rct_offsets[0];
+ if (cum1 > 1)
+ break;
+ if (first_block_offset == 0 && last_block_offset >= rct_offsets.size() - 2)
+ break;
+ // expand up to bounds
+ if (first_block_offset > 0)
+ --first_block_offset;
+ if (last_block_offset < rct_offsets.size() - 1)
+ ++last_block_offset;
+ }
+ const uint64_t n_rct = rct_offsets[last_block_offset] - (first_block_offset == 0 ? 0 : rct_offsets[first_block_offset - 1]);
if (n_rct == 0)
return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0;
- return rct_offsets[block_offset] + crypto::rand<uint64_t>() % n_rct;
+ MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset);
+ return rct_offsets[first_block_offset] + crypto::rand<uint64_t>() % n_rct;
};
size_t num_selected_transfers = 0;
@@ -6641,12 +6989,26 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
LOG_PRINT_L1("Selecting real output: " << td.m_global_output_index << " for " << print_money(amount));
}
+ std::unordered_map<const char*, std::set<uint64_t>> picks;
+
// while we still need more mixins
+ uint64_t num_usable_outs = num_outs;
+ bool allow_blackballed = false;
while (num_found < requested_outputs_count)
{
// if we've gone through every possible output, we've gotten all we can
- if (seen_indices.size() == num_outs)
- break;
+ if (seen_indices.size() == num_usable_outs)
+ {
+ // there is a first pass which rejects blackballed outputs, then a second pass
+ // which allows them if we don't have enough non blackballed outputs to reach
+ // the required amount of outputs (since consensus does not care about blackballed
+ // outputs, we still need to reach the minimum ring size)
+ if (allow_blackballed)
+ break;
+ MINFO("Not enough output not marked as spent, we'll allow outputs marked as spent");
+ allow_blackballed = true;
+ num_usable_outs = num_outs;
+ }
// get a random output index from the DB. If we've already seen it,
// return to the top of the loop and try again, otherwise add it to the
@@ -6720,14 +7082,30 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
if (seen_indices.count(i))
continue;
- if (is_output_blackballed(std::make_pair(amount, i))) // don't add blackballed outputs
+ if (!allow_blackballed && is_output_blackballed(std::make_pair(amount, i))) // don't add blackballed outputs
+ {
+ --num_usable_outs;
continue;
+ }
seen_indices.emplace(i);
- LOG_PRINT_L2("picking " << i << " as " << type);
+ picks[type].insert(i);
req.outputs.push_back({amount, i});
++num_found;
}
+
+ for (const auto &pick: picks)
+ MDEBUG("picking " << pick.first << " outputs: " <<
+ boost::join(pick.second | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " "));
+
+ // if we had enough unusable outputs, we might fall off here and still
+ // have too few outputs, so we stuff with one to keep counts good, and
+ // we'll error out later
+ while (num_found < requested_outputs_count)
+ {
+ req.outputs.push_back({amount, 0});
+ ++num_found;
+ }
}
// sort the subsection, to ensure the daemon doesn't know which output is ours
@@ -6735,8 +7113,15 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
[](const get_outputs_out &a, const get_outputs_out &b) { return a.index < b.index; });
}
- for (auto i: req.outputs)
- LOG_PRINT_L1("asking for output " << i.index << " for " << print_money(i.amount));
+ if (ELPP->vRegistry()->allowed(el::Level::Debug, MONERO_DEFAULT_LOG_CATEGORY))
+ {
+ std::map<uint64_t, std::set<uint64_t>> outs;
+ for (const auto &i: req.outputs)
+ outs[i.amount].insert(i.index);
+ for (const auto &o: outs)
+ MDEBUG("asking for outputs with amount " << print_money(o.first) << ": " <<
+ boost::join(o.second | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " "));
+ }
// get the keys for those
m_daemon_rpc_mutex.lock();
@@ -8408,7 +8793,7 @@ skip_tx:
return ptx_vector;
}
-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)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, 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)
{
std::vector<size_t> unused_transfers_indices;
std::vector<size_t> unused_dust_indices;
@@ -8459,10 +8844,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
}
}
- return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra);
+ return create_transactions_from(address, is_subaddress, outputs, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra);
}
-std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypto::key_image &ki, 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)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra)
{
std::vector<size_t> unused_transfers_indices;
std::vector<size_t> unused_dust_indices;
@@ -8480,10 +8865,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypt
break;
}
}
- return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra);
+ return create_transactions_from(address, is_subaddress, outputs, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra);
}
-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)
+std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, 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)
{
//ensure device is let in NONE mode in any case
hw::device &hwdev = m_account.get_device();
@@ -8578,7 +8963,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask);
- tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress));
+ // add N - 1 outputs for correct initial fee estimation
+ for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i)
+ 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");
@@ -8590,15 +8977,35 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask);
- available_for_fee = test_ptx.fee + test_ptx.dests[0].amount + test_ptx.change_dts.amount;
+ available_for_fee = test_ptx.fee + test_ptx.change_dts.amount;
+ for (auto &dt: test_ptx.dests)
+ available_for_fee += dt.amount;
LOG_PRINT_L2("Made a " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(available_for_fee) << " available for fee (" <<
print_money(needed_fee) << " needed)");
+ // add last output, missed for fee estimation
+ if (outputs > 1)
+ tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress));
+
THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself");
do {
LOG_PRINT_L2("We made a tx, adjusting fee and saving it");
- tx.dsts[0].amount = available_for_fee - needed_fee;
+ // distribute total transferred amount between outputs
+ uint64_t amount_transferred = available_for_fee - needed_fee;
+ uint64_t dt_amount = amount_transferred / outputs;
+ // residue is distributed as one atomic unit per output until it reaches zero
+ uint64_t residue = amount_transferred % outputs;
+ for (auto &dt: tx.dsts)
+ {
+ uint64_t dt_residue = 0;
+ if (residue > 0)
+ {
+ dt_residue = 1;
+ residue -= 1;
+ }
+ dt.amount = dt_amount + dt_residue;
+ }
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
test_tx, test_ptx, range_proof_type);
@@ -8845,7 +9252,7 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions()
unmixable_transfer_outputs.push_back(n);
}
- 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>());
+ return create_transactions_from(m_account_public_address, false, 1, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>());
}
//----------------------------------------------------------------------------------------------------
void wallet2::discard_unmixable_outputs()
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index c30ca1d85..680196f01 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -68,6 +68,7 @@ namespace tools
{
class ringdb;
class wallet2;
+ class Notify;
class wallet_keys_unlocker
{
@@ -176,7 +177,7 @@ namespace tools
static void init_options(boost::program_options::options_description& desc_params);
//! Uses stdin and stdout. Returns a wallet2 if no errors.
- static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
+ static std::pair<std::unique_ptr<wallet2>, password_container> make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
//! Uses stdin and stdout. Returns a wallet2 and password for `wallet_file` if no errors.
static std::pair<std::unique_ptr<wallet2>, password_container>
@@ -189,6 +190,7 @@ namespace tools
static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, bool unattended, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds);
+ static bool query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds = 1);
wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false);
~wallet2();
@@ -553,7 +555,7 @@ namespace tools
* \param device_name name of HW to use
* \param create_address_file Whether to create an address file
*/
- void restore(const std::string& wallet_, const epee::wipeable_string& password, const std::string &device_name, bool create_address_file);
+ void restore(const std::string& wallet_, const epee::wipeable_string& password, const std::string &device_name, bool create_address_file = false);
/*!
* \brief Creates a multisig wallet
@@ -572,6 +574,14 @@ namespace tools
const std::vector<crypto::secret_key> &view_keys,
const std::vector<crypto::public_key> &spend_keys,
uint32_t threshold);
+ std::string exchange_multisig_keys(const epee::wipeable_string &password,
+ const std::vector<std::string> &info);
+ /*!
+ * \brief Any but first round of keys exchange
+ */
+ std::string exchange_multisig_keys(const epee::wipeable_string &password,
+ std::unordered_set<crypto::public_key> pkeys,
+ std::vector<crypto::public_key> signers);
/*!
* \brief Finalizes creation of a multisig wallet
*/
@@ -709,7 +719,8 @@ namespace tools
bool has_multisig_partial_key_images() const;
bool has_unknown_key_images() const;
bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const;
- bool key_on_device() const { return m_key_on_device; }
+ bool key_on_device() const { return get_device_type() != hw::device::device_type::SOFTWARE; }
+ hw::device::device_type get_device_type() const { return m_key_device_type; }
bool reconnect_device();
// locked & unlocked balance of given or current subaddress account
@@ -750,9 +761,9 @@ namespace tools
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);
bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func);
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); // 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);
- std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, 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);
- 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);
+ std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, 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);
+ std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra);
+ std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, 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 load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func);
@@ -1174,6 +1185,8 @@ namespace tools
void change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password);
+ void set_tx_notify(const std::shared_ptr<tools::Notify> &notify) { m_tx_notify = notify; }
+
private:
/*!
* \brief Stores wallet information to wallet file.
@@ -1243,6 +1256,12 @@ namespace tools
bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution);
uint64_t get_segregation_fork_height() const;
+ void unpack_multisig_info(const std::vector<std::string>& info,
+ std::vector<crypto::public_key> &public_keys,
+ std::vector<crypto::secret_key> &secret_keys) const;
+ bool unpack_extra_multisig_info(const std::vector<std::string>& info,
+ std::vector<crypto::public_key> &signers,
+ std::unordered_set<crypto::public_key> &pkeys) const;
void cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const;
@@ -1284,7 +1303,7 @@ namespace tools
bool m_trusted_daemon;
i_wallet2_callback* m_callback;
- bool m_key_on_device;
+ hw::device::device_type m_key_device_type;
cryptonote::network_type m_nettype;
uint64_t m_kdf_rounds;
std::string seed_language; /*!< Language of the mnemonics (seed). */
@@ -1293,6 +1312,9 @@ namespace tools
bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */
uint32_t m_multisig_threshold;
std::vector<crypto::public_key> m_multisig_signers;
+ //in case of general M/N multisig wallet we should perform N - M + 1 key exchange rounds and remember how many rounds are passed.
+ uint32_t m_multisig_rounds_passed;
+ std::vector<crypto::public_key> m_multisig_derivations;
bool m_always_confirm_transfers;
bool m_print_ring_members;
bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */
@@ -1351,6 +1373,8 @@ namespace tools
boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh;
bool m_unattended;
+
+ std::shared_ptr<tools::Notify> m_tx_notify;
};
}
BOOST_CLASS_VERSION(tools::wallet2, 25)
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 86b46b173..1b63d65b6 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -1102,6 +1102,13 @@ namespace tools
return false;
}
+ if (req.outputs < 1)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_TX_NOT_POSSIBLE;
+ er.message = "Amount of outputs should be greater than 0.";
+ return false;
+ }
+
try
{
uint64_t mixin;
@@ -1114,7 +1121,7 @@ namespace tools
mixin = m_wallet->adjust_mixin(req.mixin);
}
uint32_t priority = m_wallet->adjust_priority(req.priority);
- 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, priority, extra, req.account_index, req.subaddr_indices);
+ std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices);
return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er);
@@ -1140,6 +1147,13 @@ namespace tools
return false;
}
+ if (req.outputs < 1)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_TX_NOT_POSSIBLE;
+ er.message = "Amount of outputs should be greater than 0.";
+ return false;
+ }
+
// validate the transfer requested and populate dsts & extra
std::list<wallet_rpc::transfer_destination> destination;
destination.push_back(wallet_rpc::transfer_destination());
@@ -1170,7 +1184,7 @@ namespace tools
mixin = m_wallet->adjust_mixin(req.mixin);
}
uint32_t priority = m_wallet->adjust_priority(req.priority);
- std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, priority, extra);
+ std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra);
if (ptx_vector.empty())
{
@@ -1539,8 +1553,8 @@ namespace tools
rpc_transfers.spent = td.m_spent;
rpc_transfers.global_index = td.m_global_output_index;
rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(td.m_txid);
- rpc_transfers.subaddr_index = td.m_subaddr_index.minor;
- rpc_transfers.key_image = req.verbose && td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : "";
+ rpc_transfers.subaddr_index = {td.m_subaddr_index.major, td.m_subaddr_index.minor};
+ rpc_transfers.key_image = td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : "";
res.transfers.push_back(rpc_transfers);
}
}
@@ -2159,8 +2173,8 @@ namespace tools
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
if (i->second.m_tx_hash == txid)
{
- fill_transfer_entry(res.transfer, i->second.m_tx_hash, i->first, i->second);
- return true;
+ res.transfers.resize(res.transfers.size() + 1);
+ fill_transfer_entry(res.transfers.back(), i->second.m_tx_hash, i->first, i->second);
}
}
@@ -2169,8 +2183,8 @@ namespace tools
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)
{
- fill_transfer_entry(res.transfer, i->first, i->second);
- return true;
+ res.transfers.resize(res.transfers.size() + 1);
+ fill_transfer_entry(res.transfers.back(), i->first, i->second);
}
}
@@ -2179,8 +2193,8 @@ namespace tools
for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
if (i->first == txid)
{
- fill_transfer_entry(res.transfer, i->first, i->second);
- return true;
+ res.transfers.resize(res.transfers.size() + 1);
+ fill_transfer_entry(res.transfers.back(), i->first, i->second);
}
}
@@ -2191,11 +2205,17 @@ namespace tools
for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
if (i->second.m_pd.m_tx_hash == txid)
{
- fill_transfer_entry(res.transfer, i->first, i->second);
- return true;
+ res.transfers.resize(res.transfers.size() + 1);
+ fill_transfer_entry(res.transfers.back(), i->first, i->second);
}
}
+ if (!res.transfers.empty())
+ {
+ res.transfer = res.transfers.front(); // backward compat
+ return true;
+ }
+
er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID;
er.message = "Transaction not found.";
return false;
@@ -2802,8 +2822,7 @@ namespace tools
{
try
{
- m_wallet->rewrite(m_wallet->get_wallet_file(), req.new_password);
- m_wallet->store();
+ m_wallet->change_password(m_wallet->get_wallet_file(), req.old_password, req.new_password);
LOG_PRINT_L0("Wallet password changed.");
}
catch (const std::exception& e)
@@ -3105,7 +3124,7 @@ namespace tools
return false;
}
- if (req.multisig_info.size() < threshold - 1)
+ if (req.multisig_info.size() < 1 || req.multisig_info.size() > total)
{
er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
er.message = "Needs multisig info from more participants";
@@ -3132,6 +3151,55 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ if (m_restricted)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+ bool ready;
+ uint32_t threshold, total;
+ if (!m_wallet->multisig(&ready, &threshold, &total))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+ er.message = "This wallet is not multisig";
+ return false;
+ }
+
+ if (ready)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG;
+ er.message = "This wallet is multisig, and already finalized";
+ return false;
+ }
+
+ if (req.multisig_info.size() < 1 || req.multisig_info.size() > total)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
+ er.message = "Needs multisig info from more participants";
+ return false;
+ }
+
+ try
+ {
+ res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info);
+ if (res.multisig_info.empty())
+ {
+ res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype());
+ }
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = std::string("Error calling exchange_multisig_info: ") + e.what();
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
@@ -3280,9 +3348,12 @@ class t_daemon
private:
const boost::program_options::variables_map& vm;
+ std::unique_ptr<tools::wallet_rpc_server> wrpc;
+
public:
t_daemon(boost::program_options::variables_map const & _vm)
: vm(_vm)
+ , wrpc(new tools::wallet_rpc_server)
{
}
@@ -3335,7 +3406,8 @@ public:
{
try
{
- wal = tools::wallet2::make_from_json(vm, true, from_json, password_prompt);
+ auto rc = tools::wallet2::make_from_json(vm, true, from_json, password_prompt);
+ wal = std::move(rc.first);
}
catch (const std::exception &e)
{
@@ -3372,17 +3444,16 @@ public:
return false;
}
just_dir:
- tools::wallet_rpc_server wrpc;
- if (wal) wrpc.set_wallet(wal.release());
- bool r = wrpc.init(&vm);
+ if (wal) wrpc->set_wallet(wal.release());
+ bool r = wrpc->init(&vm);
CHECK_AND_ASSERT_MES(r, false, tools::wallet_rpc_server::tr("Failed to initialize wallet RPC server"));
- tools::signal_handler::install([&wrpc](int) {
- wrpc.send_stop_signal();
+ tools::signal_handler::install([this](int) {
+ wrpc->send_stop_signal();
});
LOG_PRINT_L0(tools::wallet_rpc_server::tr("Starting wallet RPC server"));
try
{
- wrpc.run();
+ wrpc->run();
}
catch (const std::exception &e)
{
@@ -3393,7 +3464,7 @@ public:
try
{
LOG_PRINT_L0(tools::wallet_rpc_server::tr("Saving wallet..."));
- wrpc.stop();
+ wrpc->stop();
LOG_PRINT_L0(tools::wallet_rpc_server::tr("Successfully saved"));
}
catch (const std::exception& e)
@@ -3403,6 +3474,11 @@ public:
}
return true;
}
+
+ void stop()
+ {
+ wrpc->send_stop_signal();
+ }
};
class t_executor final
@@ -3410,7 +3486,9 @@ class t_executor final
public:
static std::string const NAME;
- std::string const & name()
+ typedef ::t_daemon t_daemon;
+
+ std::string const & name() const
{
return NAME;
}
@@ -3434,6 +3512,8 @@ public:
std::string const t_executor::NAME = "Wallet RPC Daemon";
int main(int argc, char** argv) {
+ TRY_ENTRY();
+
namespace po = boost::program_options;
const auto arg_wallet_file = wallet_args::arg_wallet_file();
@@ -3453,6 +3533,7 @@ int main(int argc, char** argv) {
command_line::add_arg(desc_params, arg_prompt_for_password);
daemonizer::init_options(hidden_options, desc_params);
+ desc_params.add(hidden_options);
boost::optional<po::variables_map> vm;
bool should_terminate = false;
@@ -3476,4 +3557,5 @@ int main(int argc, char** argv) {
}
return daemonizer::daemonize(argc, const_cast<const char**>(argv), t_executor{}, *vm) ? 0 : 1;
+ CATCH_ENTRY_L0("main", 1);
}
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index ab7917a78..35ea11902 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -35,6 +35,7 @@
#include <string>
#include "common/util.h"
#include "net/http_server_impl_base.h"
+#include "math_helper.h"
#include "wallet_rpc_server_commands_defs.h"
#include "wallet2.h"
@@ -141,6 +142,7 @@ namespace tools
MAP_JON_RPC_WE("export_multisig_info", on_export_multisig, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG)
MAP_JON_RPC_WE("import_multisig_info", on_import_multisig, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG)
MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG)
+ MAP_JON_RPC_WE("exchange_multisig_keys", on_exchange_multisig_keys, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS)
MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG)
MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG)
MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION)
@@ -218,6 +220,7 @@ namespace tools
bool on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er);
+ bool on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er);
bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er);
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 4501cf575..2377b69e3 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -47,7 +47,7 @@
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define WALLET_RPC_VERSION_MAJOR 1
-#define WALLET_RPC_VERSION_MINOR 3
+#define WALLET_RPC_VERSION_MINOR 4
#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR)
namespace tools
@@ -639,6 +639,7 @@ namespace wallet_rpc
uint32_t priority;
uint64_t mixin;
uint64_t ring_size;
+ uint64_t outputs;
uint64_t unlock_time;
std::string payment_id;
bool get_tx_keys;
@@ -654,6 +655,7 @@ namespace wallet_rpc
KV_SERIALIZE(priority)
KV_SERIALIZE_OPT(mixin, (uint64_t)0)
KV_SERIALIZE_OPT(ring_size, (uint64_t)0)
+ KV_SERIALIZE_OPT(outputs, (uint64_t)1)
KV_SERIALIZE(unlock_time)
KV_SERIALIZE(payment_id)
KV_SERIALIZE(get_tx_keys)
@@ -705,6 +707,7 @@ namespace wallet_rpc
uint32_t priority;
uint64_t mixin;
uint64_t ring_size;
+ uint64_t outputs;
uint64_t unlock_time;
std::string payment_id;
bool get_tx_key;
@@ -718,6 +721,7 @@ namespace wallet_rpc
KV_SERIALIZE(priority)
KV_SERIALIZE_OPT(mixin, (uint64_t)0)
KV_SERIALIZE_OPT(ring_size, (uint64_t)0)
+ KV_SERIALIZE_OPT(outputs, (uint64_t)1)
KV_SERIALIZE(unlock_time)
KV_SERIALIZE(payment_id)
KV_SERIALIZE(get_tx_key)
@@ -859,7 +863,7 @@ namespace wallet_rpc
bool spent;
uint64_t global_index;
std::string tx_hash;
- uint32_t subaddr_index;
+ cryptonote::subaddress_index subaddr_index;
std::string key_image;
BEGIN_KV_SERIALIZE_MAP()
@@ -879,13 +883,11 @@ namespace wallet_rpc
std::string transfer_type;
uint32_t account_index;
std::set<uint32_t> subaddr_indices;
- bool verbose;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(transfer_type)
KV_SERIALIZE(account_index)
KV_SERIALIZE(subaddr_indices)
- KV_SERIALIZE(verbose)
END_KV_SERIALIZE_MAP()
};
@@ -1397,9 +1399,11 @@ namespace wallet_rpc
struct response
{
transfer_entry transfer;
+ std::list<transfer_entry> transfers;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(transfer);
+ KV_SERIALIZE(transfers);
END_KV_SERIALIZE_MAP()
};
};
@@ -1986,6 +1990,31 @@ namespace wallet_rpc
};
};
+ struct COMMAND_RPC_EXCHANGE_MULTISIG_KEYS
+ {
+ struct request
+ {
+ std::string password;
+ std::vector<std::string> multisig_info;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(password)
+ KV_SERIALIZE(multisig_info)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string address;
+ std::string multisig_info;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(multisig_info)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
struct COMMAND_RPC_SIGN_MULTISIG
{
struct request