diff options
Diffstat (limited to 'src')
79 files changed, 2987 insertions, 985 deletions
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index d2fe39fc2..2c40b5a78 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1514,6 +1514,20 @@ public: virtual bool check_pruning() = 0; /** + * @brief get the max block size + */ + virtual uint64_t get_max_block_size() = 0; + + /** + * @brief add a new max block size + * + * The max block size will be the maximum of sz and the current block size + * + * @param: sz the block size + */ + + virtual void add_max_block_size(uint64_t sz) = 0; + /** * @brief runs a function over all txpool transactions * * The subclass should run the passed function for each txpool tx it has diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 3391d9bff..a07e9ac55 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -54,7 +54,7 @@ using epee::string_tools::pod_to_hex; using namespace crypto; // Increase when the DB structure changes -#define VERSION 4 +#define VERSION 5 namespace { @@ -274,7 +274,7 @@ typedef struct mdb_block_info_1 uint64_t bi_timestamp; uint64_t bi_coins; uint64_t bi_weight; // a size_t really but we need 32-bit compat - difficulty_type bi_diff; + uint64_t bi_diff; crypto::hash bi_hash; } mdb_block_info_1; @@ -284,7 +284,7 @@ typedef struct mdb_block_info_2 uint64_t bi_timestamp; uint64_t bi_coins; uint64_t bi_weight; // a size_t really but we need 32-bit compat - difficulty_type bi_diff; + uint64_t bi_diff; crypto::hash bi_hash; uint64_t bi_cum_rct; } mdb_block_info_2; @@ -295,13 +295,26 @@ typedef struct mdb_block_info_3 uint64_t bi_timestamp; uint64_t bi_coins; uint64_t bi_weight; // a size_t really but we need 32-bit compat - difficulty_type bi_diff; + uint64_t bi_diff; crypto::hash bi_hash; uint64_t bi_cum_rct; uint64_t bi_long_term_block_weight; } mdb_block_info_3; -typedef mdb_block_info_3 mdb_block_info; +typedef struct mdb_block_info_4 +{ + uint64_t bi_height; + uint64_t bi_timestamp; + uint64_t bi_coins; + uint64_t bi_weight; // a size_t really but we need 32-bit compat + uint64_t bi_diff_lo; + uint64_t bi_diff_hi; + crypto::hash bi_hash; + uint64_t bi_cum_rct; + uint64_t bi_long_term_block_weight; +} mdb_block_info_4; + +typedef mdb_block_info_4 mdb_block_info; typedef struct blk_height { crypto::hash bh_hash; @@ -757,7 +770,8 @@ void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t l bi.bi_timestamp = blk.timestamp; bi.bi_coins = coins_generated; bi.bi_weight = block_weight; - bi.bi_diff = cumulative_difficulty; + bi.bi_diff_hi = (cumulative_difficulty >> 64).convert_to<uint64_t>(); + bi.bi_diff_lo = (cumulative_difficulty << 64 >> 64).convert_to<uint64_t>(); bi.bi_hash = blk_hash; bi.bi_cum_rct = num_rct_outs; if (blk.major_version >= 4) @@ -2499,6 +2513,58 @@ std::vector<uint64_t> BlockchainLMDB::get_block_info_64bit_fields(uint64_t start return ret; } +uint64_t BlockchainLMDB::get_max_block_size() +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + TXN_PREFIX_RDONLY(); + RCURSOR(properties) + MDB_val_str(k, "max_block_size"); + MDB_val v; + int result = mdb_cursor_get(m_cur_properties, &k, &v, MDB_SET); + if (result == MDB_NOTFOUND) + return std::numeric_limits<uint64_t>::max(); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to retrieve max block size: ", result).c_str())); + if (v.mv_size != sizeof(uint64_t)) + throw0(DB_ERROR("Failed to retrieve or create max block size: unexpected value size")); + uint64_t max_block_size; + memcpy(&max_block_size, v.mv_data, sizeof(max_block_size)); + TXN_POSTFIX_RDONLY(); + return max_block_size; +} + +void BlockchainLMDB::add_max_block_size(uint64_t sz) +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + mdb_txn_cursors *m_cursors = &m_wcursors; + + CURSOR(properties) + + MDB_val_str(k, "max_block_size"); + MDB_val v; + int result = mdb_cursor_get(m_cur_properties, &k, &v, MDB_SET); + if (result && result != MDB_NOTFOUND) + throw0(DB_ERROR(lmdb_error("Failed to retrieve max block size: ", result).c_str())); + uint64_t max_block_size = 0; + if (result == 0) + { + if (v.mv_size != sizeof(uint64_t)) + throw0(DB_ERROR("Failed to retrieve or create max block size: unexpected value size")); + memcpy(&max_block_size, v.mv_data, sizeof(max_block_size)); + } + if (sz > max_block_size) + max_block_size = sz; + v.mv_data = (void*)&max_block_size; + v.mv_size = sizeof(max_block_size); + result = mdb_cursor_put(m_cur_properties, &k, &v, 0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to set max_block_size: ", result).c_str())); +} + + std::vector<uint64_t> BlockchainLMDB::get_block_weights(uint64_t start_height, size_t count) const { return get_block_info_64bit_fields(start_height, count, offsetof(mdb_block_info, bi_weight)); @@ -2527,7 +2593,9 @@ difficulty_type BlockchainLMDB::get_block_cumulative_difficulty(const uint64_t& throw0(DB_ERROR("Error attempting to retrieve a cumulative difficulty from the db")); mdb_block_info *bi = (mdb_block_info *)result.mv_data; - difficulty_type ret = bi->bi_diff; + difficulty_type ret = bi->bi_diff_hi; + ret <<= 64; + ret |= bi->bi_diff_lo; TXN_POSTFIX_RDONLY(); return ret; } @@ -5040,6 +5108,133 @@ void BlockchainLMDB::migrate_3_4() txn.commit(); } +void BlockchainLMDB::migrate_4_5() +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + uint64_t i; + int result; + mdb_txn_safe txn(false); + MDB_val k, v; + char *ptr; + + MGINFO_YELLOW("Migrating blockchain from DB version 4 to 5 - this may take a while:"); + + do { + LOG_PRINT_L1("migrating block info:"); + + result = mdb_txn_begin(m_env, NULL, 0, txn); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); + + MDB_stat db_stats; + if ((result = mdb_stat(txn, m_blocks, &db_stats))) + throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str())); + const uint64_t blockchain_height = db_stats.ms_entries; + + /* the block_info table name is the same but the old version and new version + * have incompatible data. Create a new table. We want the name to be similar + * to the old name so that it will occupy the same location in the DB. + */ + MDB_dbi o_block_info = m_block_info; + lmdb_db_open(txn, "block_infn", MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_block_info, "Failed to open db handle for block_infn"); + mdb_set_dupsort(txn, m_block_info, compare_uint64); + + + MDB_cursor *c_blocks; + result = mdb_cursor_open(txn, m_blocks, &c_blocks); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for blocks: ", result).c_str())); + + MDB_cursor *c_old, *c_cur; + i = 0; + while(1) { + if (!(i % 1000)) { + if (i) { + LOGIF(el::Level::Info) { + std::cout << i << " / " << blockchain_height << " \r" << std::flush; + } + txn.commit(); + result = mdb_txn_begin(m_env, NULL, 0, txn); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); + } + result = mdb_cursor_open(txn, m_block_info, &c_cur); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_infn: ", result).c_str())); + result = mdb_cursor_open(txn, o_block_info, &c_old); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_info: ", result).c_str())); + if (!i) { + MDB_stat db_stat; + result = mdb_stat(txn, m_block_info, &db_stats); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to query m_block_info: ", result).c_str())); + i = db_stats.ms_entries; + } + } + result = mdb_cursor_get(c_old, &k, &v, MDB_NEXT); + if (result == MDB_NOTFOUND) { + txn.commit(); + break; + } + else if (result) + throw0(DB_ERROR(lmdb_error("Failed to get a record from block_info: ", result).c_str())); + const mdb_block_info_3 *bi_old = (const mdb_block_info_3*)v.mv_data; + mdb_block_info_4 bi; + bi.bi_height = bi_old->bi_height; + bi.bi_timestamp = bi_old->bi_timestamp; + bi.bi_coins = bi_old->bi_coins; + bi.bi_weight = bi_old->bi_weight; + bi.bi_diff_lo = bi_old->bi_diff; + bi.bi_diff_hi = 0; + bi.bi_hash = bi_old->bi_hash; + bi.bi_cum_rct = bi_old->bi_cum_rct; + bi.bi_long_term_block_weight = bi_old->bi_long_term_block_weight; + + MDB_val_set(nv, bi); + result = mdb_cursor_put(c_cur, (MDB_val *)&zerokval, &nv, MDB_APPENDDUP); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to put a record into block_infn: ", result).c_str())); + /* we delete the old records immediately, so the overall DB and mapsize should not grow. + * This is a little slower than just letting mdb_drop() delete it all at the end, but + * it saves a significant amount of disk space. + */ + result = mdb_cursor_del(c_old, 0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to delete a record from block_info: ", result).c_str())); + i++; + } + + result = mdb_txn_begin(m_env, NULL, 0, txn); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); + /* Delete the old table */ + result = mdb_drop(txn, o_block_info, 1); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to delete old block_info table: ", result).c_str())); + + RENAME_DB("block_infn"); + mdb_dbi_close(m_env, m_block_info); + + lmdb_db_open(txn, "block_info", MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_block_info, "Failed to open db handle for block_infn"); + mdb_set_dupsort(txn, m_block_info, compare_uint64); + + txn.commit(); + } while(0); + + uint32_t version = 5; + v.mv_data = (void *)&version; + v.mv_size = sizeof(version); + MDB_val_str(vk, "version"); + result = mdb_txn_begin(m_env, NULL, 0, txn); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); + result = mdb_put(txn, m_properties, &vk, &v, 0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to update version for the db: ", result).c_str())); + txn.commit(); +} + void BlockchainLMDB::migrate(const uint32_t oldversion) { if (oldversion < 1) @@ -5050,6 +5245,8 @@ void BlockchainLMDB::migrate(const uint32_t oldversion) migrate_2_3(); if (oldversion < 4) migrate_3_4(); + if (oldversion < 5) + migrate_4_5(); } } // namespace cryptonote diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 9185bd409..f6b00817d 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -400,6 +400,9 @@ private: std::vector<uint64_t> get_block_info_64bit_fields(uint64_t start_height, size_t count, off_t offset) const; + uint64_t get_max_block_size(); + void add_max_block_size(uint64_t sz); + // fix up anything that may be wrong due to past bugs virtual void fixup(); @@ -418,6 +421,9 @@ private: // migrate from DB version 3 to 4 void migrate_3_4(); + // migrate from DB version 4 to 5 + void migrate_4_5(); + void cleanup_batch(); private: diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index 7916364c5..04fad26a4 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -149,6 +149,9 @@ public: virtual bool update_pruning() { return true; } virtual bool check_pruning() { return true; } virtual void prune_outputs(uint64_t amount) {} + + virtual uint64_t get_max_block_size() { return 100000000; } + virtual void add_max_block_size(uint64_t sz) { } }; } diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index e4efdc3cb..8454595ac 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -294,7 +294,8 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path } // 4 byte magic + (currently) 1024 byte header structures - bootstrap.seek_to_first_chunk(import_file); + uint8_t major_version, minor_version; + bootstrap.seek_to_first_chunk(import_file, major_version, minor_version); std::string str1; char buffer1[1024]; @@ -415,7 +416,23 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path { str1.assign(buffer_block, chunk_size); bootstrap::block_package bp; - if (! ::serialization::parse_binary(str1, bp)) + bool res; + if (major_version == 0) + { + bootstrap::block_package_1 bp1; + res = ::serialization::parse_binary(str1, bp1); + if (res) + { + bp.block = std::move(bp1.block); + bp.txs = std::move(bp1.txs); + bp.block_weight = bp1.block_weight; + bp.cumulative_difficulty = bp1.cumulative_difficulty; + bp.coins_generated = bp1.coins_generated; + } + } + else + res = ::serialization::parse_binary(str1, bp); + if (!res) throw std::runtime_error("Error in deserialization of chunk"); int display_interval = 1000; diff --git a/src/blockchain_utilities/blockchain_prune.cpp b/src/blockchain_utilities/blockchain_prune.cpp index 36080aade..8e13f2c04 100644 --- a/src/blockchain_utilities/blockchain_prune.cpp +++ b/src/blockchain_utilities/blockchain_prune.cpp @@ -611,24 +611,6 @@ int main(int argc, char* argv[]) } already_pruned = true; } - if (n == 0) - { - const uint64_t blockchain_height = core_storage[0]->get_current_blockchain_height(); - const crypto::hash hash = core_storage[0]->get_block_id_by_height(blockchain_height - 1); - cryptonote::block block; - if (core_storage[0]->get_block_by_hash(hash, block)) - { - if (block.major_version < 10) - { - time_t now = time(NULL); - if (now < 1555286400) // 15 april 2019 - { - MERROR("Pruning before v10 will confuse peers. Wait for v10 first"); - return 1; - } - } - } - } } core_storage[0]->deinit(); core_storage[0].reset(NULL); diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index 4cc84bf4a..33c26277e 100644 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -205,7 +205,7 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' char buf[8]; unsigned int i; for (i=0; i<24; i++) { - sprintf(buf, "\t%02d:00", i); + sprintf(buf, "\t%02u:00", i); std::cout << buf; } } diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp index fb9a24f5d..252c79776 100644 --- a/src/blockchain_utilities/bootstrap_file.cpp +++ b/src/blockchain_utilities/bootstrap_file.cpp @@ -124,8 +124,8 @@ bool BootstrapFile::initialize_file() *m_raw_data_file << blob; bootstrap::file_info bfi; - bfi.major_version = 0; - bfi.minor_version = 1; + bfi.major_version = 1; + bfi.minor_version = 0; bfi.header_size = header_size; bootstrap::blocks_info bbi; @@ -323,7 +323,7 @@ bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem return BootstrapFile::close(); } -uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file) +uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file, uint8_t &major_version, uint8_t &minor_version) { uint32_t file_magic; @@ -371,6 +371,8 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file) uint64_t full_header_size = sizeof(file_magic) + bfi.header_size; import_file.seekg(full_header_size); + major_version = bfi.major_version; + minor_version = bfi.minor_version; return full_header_size; } @@ -461,7 +463,8 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path, std::s } uint64_t full_header_size; // 4 byte magic + length of header structures - full_header_size = seek_to_first_chunk(import_file); + uint8_t major_version, minor_version; + full_header_size = seek_to_first_chunk(import_file, major_version, minor_version); MINFO("Scanning blockchain from bootstrap file..."); bool quit = false; diff --git a/src/blockchain_utilities/bootstrap_file.h b/src/blockchain_utilities/bootstrap_file.h index 5fb2cf366..1e6ef5d81 100644 --- a/src/blockchain_utilities/bootstrap_file.h +++ b/src/blockchain_utilities/bootstrap_file.h @@ -60,7 +60,7 @@ public: uint64_t count_bytes(std::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit); uint64_t count_blocks(const std::string& dir_path, std::streampos& start_pos, uint64_t& seek_height); uint64_t count_blocks(const std::string& dir_path); - uint64_t seek_to_first_chunk(std::ifstream& import_file); + uint64_t seek_to_first_chunk(std::ifstream& import_file, uint8_t &major_version, uint8_t &minor_version); bool store_blockchain_raw(cryptonote::Blockchain* cs, cryptonote::tx_memory_pool* txp, boost::filesystem::path& output_file, uint64_t use_block_height=0); diff --git a/src/blockchain_utilities/bootstrap_serialization.h b/src/blockchain_utilities/bootstrap_serialization.h index 554c6d56e..70b3eea7e 100644 --- a/src/blockchain_utilities/bootstrap_serialization.h +++ b/src/blockchain_utilities/bootstrap_serialization.h @@ -29,7 +29,7 @@ #pragma once #include "cryptonote_basic/cryptonote_boost_serialization.h" -#include "cryptonote_basic/difficulty.h" +#include "serialization/difficulty_type.h" namespace cryptonote @@ -66,6 +66,23 @@ namespace cryptonote END_SERIALIZE() }; + struct block_package_1 + { + cryptonote::block block; + std::vector<transaction> txs; + size_t block_weight; + uint64_t cumulative_difficulty; + uint64_t coins_generated; + + BEGIN_SERIALIZE() + FIELD(block) + FIELD(txs) + VARINT_FIELD(block_weight) + VARINT_FIELD(cumulative_difficulty) + VARINT_FIELD(coins_generated) + END_SERIALIZE() + }; + struct block_package { cryptonote::block block; @@ -78,7 +95,7 @@ namespace cryptonote FIELD(block) FIELD(txs) VARINT_FIELD(block_weight) - VARINT_FIELD(cumulative_difficulty) + FIELD(cumulative_difficulty) VARINT_FIELD(coins_generated) END_SERIALIZE() }; diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index a341a8c81..1a1155c7c 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -32,9 +32,9 @@ #include <stdlib.h> #include "include_base_utils.h" +#include "common/threadpool.h" #include <random> #include <boost/thread/mutex.hpp> -#include <boost/thread/thread.hpp> #include <boost/algorithm/string/join.hpp> #include <boost/optional.hpp> using namespace epee; @@ -523,16 +523,16 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std size_t first_index = dis(gen); // send all requests in parallel - std::vector<boost::thread> threads(dns_urls.size()); std::deque<bool> avail(dns_urls.size(), false), valid(dns_urls.size(), false); + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; for (size_t n = 0; n < dns_urls.size(); ++n) { - threads[n] = boost::thread([n, dns_urls, &records, &avail, &valid](){ + tpool.submit(&waiter,[n, dns_urls, &records, &avail, &valid](){ records[n] = tools::DNSResolver::instance().get_txt_record(dns_urls[n], avail[n], valid[n]); }); } - for (size_t n = 0; n < dns_urls.size(); ++n) - threads[n].join(); + waiter.wait(&tpool); size_t cur_index = first_index; do diff --git a/src/common/perf_timer.cpp b/src/common/perf_timer.cpp index dda498088..189eb85eb 100644 --- a/src/common/perf_timer.cpp +++ b/src/common/perf_timer.cpp @@ -88,7 +88,7 @@ namespace tools namespace tools { -el::Level performance_timer_log_level = el::Level::Debug; +el::Level performance_timer_log_level = el::Level::Info; static __thread std::vector<LoggingPerformanceTimer*> *performance_timers = NULL; @@ -97,8 +97,8 @@ void set_performance_timer_log_level(el::Level level) if (level != el::Level::Debug && level != el::Level::Trace && level != el::Level::Info && level != el::Level::Warning && level != el::Level::Error && level != el::Level::Fatal) { - MERROR("Wrong log level: " << el::LevelHelper::convertToString(level) << ", using Debug"); - level = el::Level::Debug; + MERROR("Wrong log level: " << el::LevelHelper::convertToString(level) << ", using Info"); + level = el::Level::Info; } performance_timer_log_level = level; } diff --git a/src/common/util.cpp b/src/common/util.cpp index 80b8a9e81..3388974ce 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -80,6 +80,7 @@ using namespace epee; #include <boost/filesystem.hpp> #include <boost/algorithm/string.hpp> #include <boost/asio.hpp> +#include <boost/format.hpp> #include <openssl/sha.h> #undef MONERO_DEFAULT_LOG_CATEGORY @@ -640,16 +641,16 @@ std::string get_nix_version_display_string() return res; } - std::error_code replace_file(const std::string& replacement_name, const std::string& replaced_name) + std::error_code replace_file(const std::string& old_name, const std::string& new_name) { int code; #if defined(WIN32) // Maximizing chances for success std::wstring wide_replacement_name; - try { wide_replacement_name = string_tools::utf8_to_utf16(replacement_name); } + try { wide_replacement_name = string_tools::utf8_to_utf16(old_name); } catch (...) { return std::error_code(GetLastError(), std::system_category()); } std::wstring wide_replaced_name; - try { wide_replaced_name = string_tools::utf8_to_utf16(replaced_name); } + try { wide_replaced_name = string_tools::utf8_to_utf16(new_name); } catch (...) { return std::error_code(GetLastError(), std::system_category()); } DWORD attributes = ::GetFileAttributesW(wide_replaced_name.c_str()); @@ -661,7 +662,7 @@ std::string get_nix_version_display_string() bool ok = 0 != ::MoveFileExW(wide_replacement_name.c_str(), wide_replaced_name.c_str(), MOVEFILE_REPLACE_EXISTING); code = ok ? 0 : static_cast<int>(::GetLastError()); #else - bool ok = 0 == std::rename(replacement_name.c_str(), replaced_name.c_str()); + bool ok = 0 == std::rename(old_name.c_str(), new_name.c_str()); code = ok ? 0 : errno; #endif return std::error_code(code, std::system_category()); @@ -1063,4 +1064,39 @@ std::string get_nix_version_display_string() strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); return std::string(buffer); } + + std::string get_human_readable_bytes(uint64_t bytes) + { + // Use 1024 for "kilo", 1024*1024 for "mega" and so on instead of the more modern and standard-conforming + // 1000, 1000*1000 and so on, to be consistent with other Monero code that also uses base 2 units + struct byte_map + { + const char* const format; + const std::uint64_t bytes; + }; + + static constexpr const byte_map sizes[] = + { + {"%.0f B", 1024}, + {"%.2f KB", 1024 * 1024}, + {"%.2f MB", std::uint64_t(1024) * 1024 * 1024}, + {"%.2f GB", std::uint64_t(1024) * 1024 * 1024 * 1024}, + {"%.2f TB", std::uint64_t(1024) * 1024 * 1024 * 1024 * 1024} + }; + + struct bytes_less + { + bool operator()(const byte_map& lhs, const byte_map& rhs) const noexcept + { + return lhs.bytes < rhs.bytes; + } + }; + + const auto size = std::upper_bound( + std::begin(sizes), std::end(sizes) - 1, byte_map{"", bytes}, bytes_less{} + ); + const std::uint64_t divisor = size->bytes / 1024; + return (boost::format(size->format) % (double(bytes) / divisor)).str(); + } + } diff --git a/src/common/util.h b/src/common/util.h index ef2305bf4..f6d5c9b1f 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -145,7 +145,7 @@ namespace tools bool create_directories_if_necessary(const std::string& path); /*! \brief std::rename wrapper for nix and something strange for windows. */ - std::error_code replace_file(const std::string& replacement_name, const std::string& replaced_name); + std::error_code replace_file(const std::string& old_name, const std::string& new_name); bool sanitize_locale(); @@ -244,4 +244,6 @@ namespace tools void closefrom(int fd); std::string get_human_readable_timestamp(uint64_t ts); + + std::string get_human_readable_bytes(uint64_t bytes); } diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index d22d59b36..3a4d09fd8 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -49,7 +49,7 @@ set(crypto_sources CryptonightR_JIT.c tree-hash.c) -if(ARCH_ID STREQUAL "i386" OR ARCH_ID STREQUAL "x86_64" OR ARCH_ID STREQUAL "x86-64") +if(ARCH_ID STREQUAL "i386" OR ARCH_ID STREQUAL "x86_64" OR ARCH_ID STREQUAL "x86-64" OR ARCH_ID STREQUAL "amd64") list(APPEND crypto_sources CryptonightR_template.S) endif() diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 03caafbb0..20d92bdf1 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -422,6 +422,8 @@ namespace cryptonote FIELDS(*static_cast<block_header *>(this)) FIELD(miner_tx) FIELD(tx_hashes) + if (tx_hashes.size() > CRYPTONOTE_MAX_TX_PER_BLOCK) + return false; END_SERIALIZE() }; diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index e336cc1d1..d8de65b81 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -76,11 +76,6 @@ namespace cryptonote { return CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; } //----------------------------------------------------------------------------------------------- - size_t get_max_block_size() - { - return CRYPTONOTE_MAX_BLOCK_SIZE; - } - //----------------------------------------------------------------------------------------------- size_t get_max_tx_size() { return CRYPTONOTE_MAX_TX_SIZE; diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index 036273f0e..c7198a16f 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -87,7 +87,6 @@ namespace cryptonote { /* Cryptonote helper functions */ /************************************************************************/ size_t get_min_block_weight(uint8_t version); - size_t get_max_block_size(); size_t get_max_tx_size(); bool get_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint8_t version); uint8_t get_account_address_checksum(const public_address_outer_blob& bl); diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 1840b6d2b..3dd98f0c6 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -40,6 +40,7 @@ #include <boost/archive/portable_binary_iarchive.hpp> #include <boost/archive/portable_binary_oarchive.hpp> #include "cryptonote_basic.h" +#include "difficulty.h" #include "common/unordered_containers_boost_serialization.h" #include "crypto/crypto.h" #include "ringct/rctTypes.h" @@ -346,6 +347,34 @@ namespace boost a & x.range_proof_type; a & x.bp_version; } + + template <class Archive> + inline void serialize(Archive &a, cryptonote::difficulty_type &x, const boost::serialization::version_type ver) + { + if (Archive::is_loading::value) + { + // load high part + uint64_t v = 0; + a & v; + x = v; + // load low part + x = x << 64; + a & v; + x += v; + } + else + { + // store high part + cryptonote::difficulty_type x_ = x >> 64; + uint64_t v = x_.convert_to<uint64_t>(); + a & v; + // store low part + x_ = x << 64 >> 64; + v = x_.convert_to<uint64_t>(); + a & v; + } + } + } } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 094057b1f..566622c1a 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1065,8 +1065,6 @@ namespace cryptonote // prefix get_transaction_prefix_hash(t, hashes[0]); - transaction &tt = const_cast<transaction&>(t); - const blobdata blob = tx_to_blob(t); const unsigned int unprunable_size = t.unprunable_size; const unsigned int prefix_size = t.prefix_size; @@ -1090,7 +1088,14 @@ namespace cryptonote // we still need the size if (blob_size) - *blob_size = get_object_blobsize(t); + { + if (!t.is_blob_size_valid()) + { + t.blob_size = blob.size(); + t.set_blob_size_valid(true); + } + *blob_size = t.blob_size; + } return true; } @@ -1143,21 +1148,37 @@ namespace cryptonote return blob; } //--------------------------------------------------------------- - bool calculate_block_hash(const block& b, crypto::hash& res) + bool calculate_block_hash(const block& b, crypto::hash& res, const blobdata *blob) { + blobdata bd; + if (!blob) + { + bd = block_to_blob(b); + blob = &bd; + } + + bool hash_result = get_object_hash(get_block_hashing_blob(b), res); + if (!hash_result) + return false; + + if (b.miner_tx.vin.size() == 1 && b.miner_tx.vin[0].type() == typeid(cryptonote::txin_gen)) + { + const cryptonote::txin_gen &txin_gen = boost::get<cryptonote::txin_gen>(b.miner_tx.vin[0]); + if (txin_gen.height != 202612) + return true; + } + // EXCEPTION FOR BLOCK 202612 const std::string correct_blob_hash_202612 = "3a8a2b3a29b50fc86ff73dd087ea43c6f0d6b8f936c849194d5c84c737903966"; const std::string existing_block_id_202612 = "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"; - crypto::hash block_blob_hash = get_blob_hash(block_to_blob(b)); + crypto::hash block_blob_hash = get_blob_hash(*blob); if (string_tools::pod_to_hex(block_blob_hash) == correct_blob_hash_202612) { string_tools::hex_to_pod(existing_block_id_202612, res); return true; } - bool hash_result = get_object_hash(get_block_hashing_blob(b), res); - if (hash_result) { // make sure that we aren't looking at a block with the 202612 block id but not the correct blobdata if (string_tools::pod_to_hex(res) == existing_block_id_202612) @@ -1200,9 +1221,9 @@ namespace cryptonote bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height) { // block 202612 bug workaround - const std::string longhash_202612 = "84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000"; if (height == 202612) { + static const std::string longhash_202612 = "84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000"; string_tools::hex_to_pod(longhash_202612, res); return true; } @@ -1239,7 +1260,7 @@ namespace cryptonote return p; } //--------------------------------------------------------------- - bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b) + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b, crypto::hash *block_hash) { std::stringstream ss; ss << b_blob; @@ -1248,9 +1269,26 @@ namespace cryptonote CHECK_AND_ASSERT_MES(r, false, "Failed to parse block from blob"); b.invalidate_hashes(); b.miner_tx.invalidate_hashes(); + if (block_hash) + { + calculate_block_hash(b, *block_hash, &b_blob); + ++block_hashes_calculated_count; + b.hash = *block_hash; + b.set_hash_valid(true); + } return true; } //--------------------------------------------------------------- + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b) + { + return parse_and_validate_block_from_blob(b_blob, b, NULL); + } + //--------------------------------------------------------------- + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b, crypto::hash &block_hash) + { + return parse_and_validate_block_from_blob(b_blob, b, &block_hash); + } + //--------------------------------------------------------------- blobdata block_to_blob(const block& b) { return t_serializable_object_to_blob(b); @@ -1286,6 +1324,7 @@ namespace cryptonote crypto::hash get_tx_tree_hash(const block& b) { std::vector<crypto::hash> txs_ids; + txs_ids.reserve(1 + b.tx_hashes.size()); crypto::hash h = null_hash; size_t bl_sz = 0; get_transaction_hash(b.miner_tx, h, bl_sz); diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 40a9907be..c9de2a56e 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -114,12 +114,14 @@ namespace cryptonote crypto::hash get_pruned_transaction_hash(const transaction& t, const crypto::hash &pruned_data_hash); blobdata get_block_hashing_blob(const block& b); - bool calculate_block_hash(const block& b, crypto::hash& res); + bool calculate_block_hash(const block& b, crypto::hash& res, const blobdata *blob = NULL); bool get_block_hash(const block& b, crypto::hash& res); crypto::hash get_block_hash(const block& b); bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height); crypto::hash get_block_longhash(const block& b, uint64_t height); + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b, crypto::hash *block_hash); bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b); + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b, crypto::hash &block_hash); bool get_inputs_money_amount(const transaction& tx, uint64_t& money); uint64_t get_outs_money_amount(const transaction& tx); bool check_inputs_types_supported(const transaction& tx); @@ -140,6 +142,16 @@ namespace cryptonote std::string print_money(uint64_t amount, unsigned int decimal_point = -1); //--------------------------------------------------------------- template<class t_object> + bool t_serializable_object_from_blob(t_object& to, const blobdata& b_blob) + { + std::stringstream ss; + ss << b_blob; + binary_archive<false> ba(ss); + bool r = ::serialization::serialize(ba, to); + return r; + } + //--------------------------------------------------------------- + template<class t_object> bool t_serializable_object_to_blob(const t_object& to, blobdata& b_blob) { std::stringstream ss; diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index 89b748a83..5162e53e6 100644 --- a/src/cryptonote_basic/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -102,7 +102,7 @@ namespace cryptonote { return a + b < a || (c && a + b == (uint64_t) -1); } - bool check_hash(const crypto::hash &hash, difficulty_type difficulty) { + bool check_hash_64(const crypto::hash &hash, uint64_t difficulty) { uint64_t low, high, top, cur; // First check the highest word, this will most likely fail for a random hash. mul(swap64le(((const uint64_t *) &hash)[3]), difficulty, top, high); @@ -119,7 +119,7 @@ namespace cryptonote { return !carry; } - difficulty_type next_difficulty(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) { + uint64_t next_difficulty_64(std::vector<std::uint64_t> timestamps, std::vector<uint64_t> cumulative_difficulties, size_t target_seconds) { if(timestamps.size() > DIFFICULTY_WINDOW) { @@ -150,7 +150,7 @@ namespace cryptonote { if (time_span == 0) { time_span = 1; } - difficulty_type total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin]; + uint64_t total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin]; assert(total_work > 0); uint64_t low, high; mul(total_work, target_seconds, low, high); @@ -162,4 +162,81 @@ namespace cryptonote { return (low + time_span - 1) / time_span; } +#if defined(_MSC_VER) +#ifdef max +#undef max +#endif +#endif + + const difficulty_type max64bit(std::numeric_limits<std::uint64_t>::max()); + const boost::multiprecision::uint256_t max128bit(std::numeric_limits<boost::multiprecision::uint128_t>::max()); + const boost::multiprecision::uint512_t max256bit(std::numeric_limits<boost::multiprecision::uint256_t>::max()); + +#define FORCE_FULL_128_BITS + + bool check_hash_128(const crypto::hash &hash, difficulty_type difficulty) { +#ifndef FORCE_FULL_128_BITS + // fast check + if (difficulty >= max64bit && ((const uint64_t *) &hash)[3] > 0) + return false; +#endif + // usual slow check + boost::multiprecision::uint512_t hashVal = 0; +#ifdef FORCE_FULL_128_BITS + for(int i = 0; i < 4; i++) { // highest word is zero +#else + for(int i = 1; i < 4; i++) { // highest word is zero +#endif + hashVal <<= 64; + hashVal |= swap64le(((const uint64_t *) &hash)[3 - i]); + } + return hashVal * difficulty <= max256bit; + } + + bool check_hash(const crypto::hash &hash, difficulty_type difficulty) { + if (difficulty <= max64bit) // if can convert to small difficulty - do it + return check_hash_64(hash, difficulty.convert_to<std::uint64_t>()); + else + return check_hash_128(hash, difficulty); + } + + difficulty_type next_difficulty(std::vector<uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) { + //cutoff DIFFICULTY_LAG + if(timestamps.size() > DIFFICULTY_WINDOW) + { + timestamps.resize(DIFFICULTY_WINDOW); + cumulative_difficulties.resize(DIFFICULTY_WINDOW); + } + + + size_t length = timestamps.size(); + assert(length == cumulative_difficulties.size()); + if (length <= 1) { + return 1; + } + static_assert(DIFFICULTY_WINDOW >= 2, "Window is too small"); + assert(length <= DIFFICULTY_WINDOW); + sort(timestamps.begin(), timestamps.end()); + size_t cut_begin, cut_end; + static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Cut length is too large"); + if (length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) { + cut_begin = 0; + cut_end = length; + } else { + cut_begin = (length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) + 1) / 2; + cut_end = cut_begin + (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT); + } + assert(/*cut_begin >= 0 &&*/ cut_begin + 2 <= cut_end && cut_end <= length); + uint64_t time_span = timestamps[cut_end - 1] - timestamps[cut_begin]; + if (time_span == 0) { + time_span = 1; + } + difficulty_type total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin]; + assert(total_work > 0); + boost::multiprecision::uint256_t res = (boost::multiprecision::uint256_t(total_work) * target_seconds + time_span - 1) / time_span; + if(res > max128bit) + return 0; // to behave like previous implementation, may be better return max128bit? + return res.convert_to<difficulty_type>(); + } + } diff --git a/src/cryptonote_basic/difficulty.h b/src/cryptonote_basic/difficulty.h index 8da355b22..f7a9376fb 100644 --- a/src/cryptonote_basic/difficulty.h +++ b/src/cryptonote_basic/difficulty.h @@ -32,12 +32,13 @@ #include <cstdint> #include <vector> +#include <boost/multiprecision/cpp_int.hpp> #include "crypto/hash.h" namespace cryptonote { - typedef std::uint64_t difficulty_type; + typedef boost::multiprecision::uint128_t difficulty_type; /** * @brief checks if a hash fits the given difficulty @@ -51,6 +52,10 @@ namespace cryptonote * * @return true if valid, else false */ + bool check_hash_64(const crypto::hash &hash, uint64_t difficulty); + uint64_t next_difficulty_64(std::vector<std::uint64_t> timestamps, std::vector<uint64_t> cumulative_difficulties, size_t target_seconds); + + bool check_hash_128(const crypto::hash &hash, difficulty_type difficulty); bool check_hash(const crypto::hash &hash, difficulty_type difficulty); difficulty_type next_difficulty(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds); } diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 6628c8448..e6c6bddb6 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -106,6 +106,7 @@ namespace cryptonote m_thread_index(0), m_phandler(phandler), m_height(0), + m_threads_active(0), m_pausers_count(0), m_threads_total(0), m_starter_nonce(0), @@ -264,8 +265,8 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_threads_lock); boost::interprocess::ipcdetail::atomic_write32(&m_stop, 1); - for(boost::thread& th: m_threads) - th.join(); + while (m_threads_active > 0) + misc_utils::sleep_no_w(100); m_threads.clear(); } boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); @@ -435,26 +436,29 @@ namespace cryptonote { MTRACE("Miner has received stop signal"); - if (!is_mining()) + CRITICAL_REGION_LOCAL(m_threads_lock); + bool mining = !m_threads.empty(); + if (!mining) { MTRACE("Not mining - nothing to stop" ); return true; } send_stop_signal(); - CRITICAL_REGION_LOCAL(m_threads_lock); // In case background mining was active and the miner threads are waiting // on the background miner to signal start. - m_is_background_mining_started_cond.notify_all(); - - for(boost::thread& th: m_threads) - th.join(); + while (m_threads_active > 0) + { + m_is_background_mining_started_cond.notify_all(); + misc_utils::sleep_no_w(100); + } // The background mining thread could be sleeping for a long time, so we // interrupt it just in case m_background_mining_thread.interrupt(); m_background_mining_thread.join(); + m_is_background_mining_enabled = false; MINFO("Mining has been stopped, " << m_threads.size() << " finished" ); m_threads.clear(); @@ -572,7 +576,8 @@ namespace cryptonote //we lucky! ++m_config.current_extra_message_index; MGINFO_GREEN("Found block " << get_block_hash(b) << " at height " << height << " for difficulty: " << local_diff); - if(!m_phandler->handle_block_found(b)) + cryptonote::block_verification_context bvc; + if(!m_phandler->handle_block_found(b, bvc) || !bvc.m_added_to_main_chain) { --m_config.current_extra_message_index; }else @@ -588,6 +593,7 @@ namespace cryptonote } slow_hash_free_state(); MGINFO("Miner thread stopped ["<< th_local_index << "]"); + --m_threads_active; return true; } //----------------------------------------------------------------------------------------------------- @@ -746,10 +752,10 @@ namespace cryptonote uint8_t idle_percentage = get_percent_of_total(idle_diff, total_diff); uint8_t process_percentage = get_percent_of_total(process_diff, total_diff); - MGINFO("idle percentage is " << unsigned(idle_percentage) << "\%, miner percentage is " << unsigned(process_percentage) << "\%, ac power : " << on_ac_power); + MDEBUG("idle percentage is " << unsigned(idle_percentage) << "\%, miner percentage is " << unsigned(process_percentage) << "\%, ac power : " << on_ac_power); if( idle_percentage + process_percentage < get_idle_threshold() || !on_ac_power ) { - MGINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining stopping, thanks for your contribution!"); + MINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining stopping, thanks for your contribution!"); m_is_background_mining_started = false; // reset process times @@ -787,10 +793,10 @@ namespace cryptonote uint64_t idle_diff = (current_idle_time - prev_idle_time); uint8_t idle_percentage = get_percent_of_total(idle_diff, total_diff); - MGINFO("idle percentage is " << unsigned(idle_percentage)); + MDEBUG("idle percentage is " << unsigned(idle_percentage)); if( idle_percentage >= get_idle_threshold() && on_ac_power ) { - MGINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining started, good luck!"); + MINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining started, good luck!"); m_is_background_mining_started = true; m_is_background_mining_started_cond.notify_all(); @@ -1048,7 +1054,12 @@ namespace cryptonote if (boost::logic::indeterminate(on_battery)) { - LOG_ERROR("couldn't query power status from " << power_supply_class_path); + static bool error_shown = false; + if (!error_shown) + { + LOG_ERROR("couldn't query power status from " << power_supply_class_path); + error_shown = true; + } } return on_battery; diff --git a/src/cryptonote_basic/miner.h b/src/cryptonote_basic/miner.h index 08b1bd7f1..285075f51 100644 --- a/src/cryptonote_basic/miner.h +++ b/src/cryptonote_basic/miner.h @@ -34,6 +34,7 @@ #include <boost/logic/tribool_fwd.hpp> #include <atomic> #include "cryptonote_basic.h" +#include "verification_context.h" #include "difficulty.h" #include "math_helper.h" #ifdef _WIN32 @@ -45,7 +46,7 @@ namespace cryptonote struct i_miner_handler { - virtual bool handle_block_found(block& b) = 0; + virtual bool handle_block_found(block& b, block_verification_context &bvc) = 0; virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) = 0; protected: ~i_miner_handler(){}; @@ -125,6 +126,7 @@ namespace cryptonote uint64_t m_height; volatile uint32_t m_thread_index; volatile uint32_t m_threads_total; + std::atomic<uint32_t> m_threads_active; std::atomic<int32_t> m_pausers_count; epee::critical_section m_miners_count_lock; diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index b6087de22..56b6a63b7 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -37,9 +37,9 @@ #define CRYPTONOTE_DNS_TIMEOUT_MS 20000 #define CRYPTONOTE_MAX_BLOCK_NUMBER 500000000 -#define CRYPTONOTE_MAX_BLOCK_SIZE 500000000 // block header blob limit, never used! #define CRYPTONOTE_GETBLOCKTEMPLATE_MAX_BLOCK_SIZE 196608 //size of block (bytes) that is the maximum that miners will produce -#define CRYPTONOTE_MAX_TX_SIZE 1000000000 +#define CRYPTONOTE_MAX_TX_SIZE 1000000 +#define CRYPTONOTE_MAX_TX_PER_BLOCK 0x10000000 #define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 #define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 60 #define CURRENT_TRANSACTION_VERSION 2 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index f5bd9bbb5..7ef8f8c45 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -178,6 +178,7 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool) : m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_sync_on_blocks(true), m_db_sync_threshold(1), m_db_sync_mode(db_async), m_db_default_sync(false), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_bytes_to_sync(0), m_cancel(false), m_long_term_block_weights_window(CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE), m_long_term_effective_median_block_weight(0), + m_long_term_block_weights_cache_tip_hash(crypto::null_hash), m_difficulty_for_next_block_top_hash(crypto::null_hash), m_difficulty_for_next_block(1), m_btc_valid(false) @@ -645,6 +646,8 @@ block Blockchain::pop_block_from_blockchain() block popped_block; std::vector<transaction> popped_txs; + CHECK_AND_ASSERT_THROW_MES(m_db->height() > 1, "Cannot pop the genesis block"); + try { m_db->pop_block(popped_block, popped_txs); @@ -1005,7 +1008,7 @@ bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain, //------------------------------------------------------------------ // This function attempts to switch to an alternate chain, returning // boolean based on success therein. -bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::iterator>& alt_chain, bool discard_disconnected_chain) +bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::const_iterator>& alt_chain, bool discard_disconnected_chain) { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -1106,7 +1109,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash:: //------------------------------------------------------------------ // This function calculates the difficulty target for the block being added to // an alternate chain. -difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei) const +difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::const_iterator>& alt_chain, block_extended_info& bei) const { if (m_fixed_difficulty) { @@ -1279,7 +1282,50 @@ void Blockchain::get_long_term_block_weights(std::vector<uint64_t>& weights, uin LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); + PERF_TIMER(get_long_term_block_weights); + + if (count == 0) + return; + + bool cached = false; + uint64_t blockchain_height = m_db->height(); + uint64_t tip_height = start_height + count - 1; + crypto::hash tip_hash = crypto::null_hash; + if (tip_height < blockchain_height && count == m_long_term_block_weights_cache.size()) + { + tip_hash = m_db->get_block_hash_from_height(tip_height); + cached = tip_hash == m_long_term_block_weights_cache_tip_hash; + } + + if (cached) + { + MTRACE("requesting " << count << " from " << start_height << ", cached"); + weights = m_long_term_block_weights_cache; + return; + } + + // in the vast majority of uncached cases, most is still cached, + // as we just move the window one block up: + if (tip_height > 0 && count == m_long_term_block_weights_cache.size() && tip_height < blockchain_height) + { + crypto::hash old_tip_hash = m_db->get_block_hash_from_height(tip_height - 1); + if (old_tip_hash == m_long_term_block_weights_cache_tip_hash) + { + weights = m_long_term_block_weights_cache; + for (size_t i = 1; i < weights.size(); ++i) + weights[i - 1] = weights[i]; + MTRACE("requesting " << count << " from " << start_height << ", incremental"); + weights.back() = m_db->get_block_long_term_weight(tip_height); + m_long_term_block_weights_cache = weights; + m_long_term_block_weights_cache_tip_hash = tip_hash; + return; + } + } + + MTRACE("requesting " << count << " from " << start_height << ", uncached"); weights = m_db->get_long_term_block_weights(start_height, count); + m_long_term_block_weights_cache = weights; + m_long_term_block_weights_cache_tip_hash = tip_hash; } //------------------------------------------------------------------ uint64_t Blockchain::get_current_cumulative_block_weight_limit() const @@ -1305,7 +1351,7 @@ uint64_t Blockchain::get_current_cumulative_block_weight_median() const // in a lot of places. That flag is not referenced in any of the code // nor any of the makefiles, howeve. Need to look into whether or not it's // necessary at all. -bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) +bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) { LOG_PRINT_L3("Blockchain::" << __func__); size_t median_weight; @@ -1315,8 +1361,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m m_tx_pool.lock(); const auto unlock_guard = epee::misc_utils::create_scope_leave_handler([&]() { m_tx_pool.unlock(); }); CRITICAL_REGION_LOCAL(m_blockchain_lock); - height = m_db->height(); - if (m_btc_valid) { + if (m_btc_valid && !from_block) { // The pool cookie is atomic. The lack of locking is OK, as if it changes // just as we compare it, we'll just use a slightly old template, but // this would be the case anyway if we'd lock, and the change happened @@ -1326,16 +1371,79 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m m_btc.timestamp = time(NULL); // update timestamp unconditionally b = m_btc; diffic = m_btc_difficulty; + height = m_btc_height; expected_reward = m_btc_expected_reward; return true; } - MDEBUG("Not using cached template: address " << (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address))) << ", nonce " << (m_btc_nonce == ex_nonce) << ", cookie " << (m_btc_pool_cookie == m_tx_pool.cookie())); + MDEBUG("Not using cached template: address " << (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address))) << ", nonce " << (m_btc_nonce == ex_nonce) << ", cookie " << (m_btc_pool_cookie == m_tx_pool.cookie()) << ", from_block " << (!!from_block)); invalidate_block_template_cache(); } - b.major_version = m_hardfork->get_current_version(); - b.minor_version = m_hardfork->get_ideal_version(); - b.prev_id = get_tail_id(); + if (from_block) + { + //build alternative subchain, front -> mainchain, back -> alternative head + //block is not related with head of main chain + //first of all - look in alternative chains container + auto it_prev = m_alternative_chains.find(*from_block); + bool parent_in_main = m_db->block_exists(*from_block); + if(it_prev == m_alternative_chains.end() && !parent_in_main) + { + MERROR("Unknown from block"); + return false; + } + + //we have new block in alternative chain + std::list<blocks_ext_by_hash::const_iterator> alt_chain; + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + std::vector<uint64_t> timestamps; + if (!build_alt_chain(*from_block, alt_chain, timestamps, bvc)) + return false; + + if (parent_in_main) + { + cryptonote::block prev_block; + CHECK_AND_ASSERT_MES(get_block_by_hash(*from_block, prev_block), false, "From block not found"); // TODO + uint64_t from_block_height = cryptonote::get_block_height(prev_block); + height = from_block_height + 1; + } + else + { + height = alt_chain.back()->second.height + 1; + } + b.major_version = m_hardfork->get_ideal_version(height); + b.minor_version = m_hardfork->get_ideal_version(); + b.prev_id = *from_block; + + // cheat and use the weight of the block we start from, virtually certain to be acceptable + // and use 1.9 times rather than 2 times so we're even more sure + if (parent_in_main) + { + median_weight = m_db->get_block_weight(height - 1); + already_generated_coins = m_db->get_block_already_generated_coins(height - 1); + } + else + { + median_weight = it_prev->second.block_cumulative_weight - it_prev->second.block_cumulative_weight / 20; + already_generated_coins = alt_chain.back()->second.already_generated_coins; + } + + // FIXME: consider moving away from block_extended_info at some point + block_extended_info bei = boost::value_initialized<block_extended_info>(); + bei.bl = b; + bei.height = alt_chain.size() ? it_prev->second.height + 1 : m_db->get_block_height(*from_block) + 1; + + diffic = get_next_difficulty_for_alternative_chain(alt_chain, bei); + } + else + { + height = m_db->height(); + b.major_version = m_hardfork->get_current_version(); + b.minor_version = m_hardfork->get_ideal_version(); + b.prev_id = get_tail_id(); + median_weight = m_current_block_cumul_weight_limit / 2; + diffic = get_difficulty_for_next_block(); + already_generated_coins = m_db->get_block_already_generated_coins(height - 1); + } b.timestamp = time(NULL); uint64_t median_ts; @@ -1344,15 +1452,11 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m b.timestamp = median_ts; } - diffic = get_difficulty_for_next_block(); CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead."); - median_weight = m_current_block_cumul_weight_limit / 2; - already_generated_coins = m_db->get_block_already_generated_coins(height - 1); - size_t txs_weight; uint64_t fee; - if (!m_tx_pool.fill_block_template(b, median_weight, already_generated_coins, txs_weight, fee, expected_reward, m_hardfork->get_current_version())) + if (!m_tx_pool.fill_block_template(b, median_weight, already_generated_coins, txs_weight, fee, expected_reward, b.major_version)) { return false; } @@ -1415,7 +1519,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m block weight, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block weight */ //make blocks coin-base tx looks close to real coinbase tx to get truthful blob weight - uint8_t hf_version = m_hardfork->get_current_version(); + uint8_t hf_version = b.major_version; size_t max_outs = hf_version >= 4 ? 1 : 11; bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance"); @@ -1470,16 +1574,22 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m ", cumulative weight " << cumulative_weight << " is now good"); #endif - cache_block_template(b, miner_address, ex_nonce, diffic, expected_reward, pool_cookie); + if (!from_block) + cache_block_template(b, miner_address, ex_nonce, diffic, height, expected_reward, pool_cookie); return true; } LOG_ERROR("Failed to create_block_template with " << 10 << " tries"); return false; } //------------------------------------------------------------------ +bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) +{ + return create_block_template(b, NULL, miner_address, diffic, height, expected_reward, ex_nonce); +} +//------------------------------------------------------------------ // for an alternate chain, get the timestamps from the main chain to complete // the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. -bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vector<uint64_t>& timestamps) +bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vector<uint64_t>& timestamps) const { LOG_PRINT_L3("Blockchain::" << __func__); @@ -1499,6 +1609,52 @@ bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vect return true; } //------------------------------------------------------------------ +bool Blockchain::build_alt_chain(const crypto::hash &prev_id, std::list<blocks_ext_by_hash::const_iterator>& alt_chain, std::vector<uint64_t> ×tamps, block_verification_context& bvc) const +{ + //build alternative subchain, front -> mainchain, back -> alternative head + blocks_ext_by_hash::const_iterator alt_it = m_alternative_chains.find(prev_id); + timestamps.clear(); + while(alt_it != m_alternative_chains.end()) + { + alt_chain.push_front(alt_it); + timestamps.push_back(alt_it->second.bl.timestamp); + alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id); + } + + // if block to be added connects to known blocks that aren't part of the + // main chain -- that is, if we're adding on to an alternate chain + if(!alt_chain.empty()) + { + // make sure alt chain doesn't somehow start past the end of the main chain + CHECK_AND_ASSERT_MES(m_db->height() > alt_chain.front()->second.height, false, "main blockchain wrong height"); + + // make sure that the blockchain contains the block that should connect + // this alternate chain with it. + if (!m_db->block_exists(alt_chain.front()->second.bl.prev_id)) + { + MERROR("alternate chain does not appear to connect to main chain..."); + return false; + } + + // make sure block connects correctly to the main chain + auto h = m_db->get_block_hash_from_height(alt_chain.front()->second.height - 1); + CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prev_id, false, "alternative chain has wrong connection to main chain"); + complete_timestamps_vector(m_db->get_block_height(alt_chain.front()->second.bl.prev_id), timestamps); + } + // if block not associated with known alternate chain + else + { + // if block parent is not part of main chain or an alternate chain, + // we ignore it + bool parent_in_main = m_db->block_exists(prev_id); + CHECK_AND_ASSERT_MES(parent_in_main, false, "internal error: broken imperative condition: parent_in_main"); + + complete_timestamps_vector(m_db->get_block_height(prev_id), timestamps); + } + + return true; +} +//------------------------------------------------------------------ // If a block is to be added and its parent block is not the current // main chain top block, then we need to see if we know about its parent block. // If its parent block is part of a known forked chain, then we need to see @@ -1543,47 +1699,18 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id if(it_prev != m_alternative_chains.end() || parent_in_main) { //we have new block in alternative chain - - //build alternative subchain, front -> mainchain, back -> alternative head - blocks_ext_by_hash::iterator alt_it = it_prev; //m_alternative_chains.find() - std::list<blocks_ext_by_hash::iterator> alt_chain; + std::list<blocks_ext_by_hash::const_iterator> alt_chain; std::vector<uint64_t> timestamps; - while(alt_it != m_alternative_chains.end()) - { - alt_chain.push_front(alt_it); - timestamps.push_back(alt_it->second.bl.timestamp); - alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id); - } - - // if block to be added connects to known blocks that aren't part of the - // main chain -- that is, if we're adding on to an alternate chain - if(!alt_chain.empty()) - { - // make sure alt chain doesn't somehow start past the end of the main chain - CHECK_AND_ASSERT_MES(m_db->height() > alt_chain.front()->second.height, false, "main blockchain wrong height"); - - // make sure that the blockchain contains the block that should connect - // this alternate chain with it. - if (!m_db->block_exists(alt_chain.front()->second.bl.prev_id)) - { - MERROR("alternate chain does not appear to connect to main chain..."); - return false; - } - - // make sure block connects correctly to the main chain - auto h = m_db->get_block_hash_from_height(alt_chain.front()->second.height - 1); - CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prev_id, false, "alternative chain has wrong connection to main chain"); - complete_timestamps_vector(m_db->get_block_height(alt_chain.front()->second.bl.prev_id), timestamps); - } - // if block not associated with known alternate chain - else - { - // if block parent is not part of main chain or an alternate chain, - // we ignore it - CHECK_AND_ASSERT_MES(parent_in_main, false, "internal error: broken imperative condition: parent_in_main"); + if (!build_alt_chain(b.prev_id, alt_chain, timestamps, bvc)) + return false; - complete_timestamps_vector(m_db->get_block_height(b.prev_id), timestamps); - } + // FIXME: consider moving away from block_extended_info at some point + block_extended_info bei = boost::value_initialized<block_extended_info>(); + bei.bl = b; + const uint64_t prev_height = alt_chain.size() ? it_prev->second.height : m_db->get_block_height(b.prev_id); + bei.height = prev_height + 1; + uint64_t block_reward = get_outs_money_amount(b.miner_tx); + bei.already_generated_coins = block_reward + (alt_chain.size() ? it_prev->second.already_generated_coins : m_db->get_block_already_generated_coins(prev_height)); // verify that the block's timestamp is within the acceptable range // (not earlier than the median of the last X blocks) @@ -1594,11 +1721,6 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id return false; } - // FIXME: consider moving away from block_extended_info at some point - block_extended_info bei = boost::value_initialized<block_extended_info>(); - bei.bl = b; - bei.height = alt_chain.size() ? it_prev->second.height + 1 : m_db->get_block_height(b.prev_id) + 1; - bool is_a_checkpoint; if(!m_checkpoints.check_block(bei.height, id, is_a_checkpoint)) { @@ -1997,7 +2119,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc return true; } //------------------------------------------------------------------ -uint64_t Blockchain::block_difficulty(uint64_t i) const +difficulty_type Blockchain::block_difficulty(uint64_t i) const { LOG_PRINT_L3("Blockchain::" << __func__); // WARNING: this function does not take m_blockchain_lock, and thus should only call read only @@ -2196,7 +2318,11 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, resp.start_height, resp.total_height); if (result) - resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(resp.total_height - 1); + { + cryptonote::difficulty_type wide_cumulative_difficulty = m_db->get_block_cumulative_difficulty(resp.total_height - 1); + resp.cumulative_difficulty = (wide_cumulative_difficulty << 64 >> 64).convert_to<uint64_t>(); + resp.cumulative_difficulty_top64 = (wide_cumulative_difficulty >> 64).convert_to<uint64_t>(); + } return result; } @@ -3126,6 +3252,7 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const if (version >= HF_VERSION_DYNAMIC_FEE) { median = m_current_block_cumul_weight_limit / 2; + const uint64_t blockchain_height = m_db->height(); already_generated_coins = blockchain_height ? m_db->get_block_already_generated_coins(blockchain_height - 1) : 0; if (!get_block_reward(median, 1, already_generated_coins, base_reward, version)) return false; @@ -3638,6 +3765,8 @@ leave: //TODO: why is this done? make sure that keeping invalid blocks makes sense. add_block_as_invalid(bl, id); MERROR_VER("Block with id " << id << " added as invalid because of wrong inputs in transactions"); + MERROR_VER("tx_index " << tx_index << ", m_blocks_txs_check " << m_blocks_txs_check.size() << ":"); + for (const auto &h: m_blocks_txs_check) MERROR_VER(" " << h); bvc.m_verifivation_failed = true; return_tx_to_pool(txs); goto leave; @@ -3767,12 +3896,6 @@ leave: //------------------------------------------------------------------ bool Blockchain::prune_blockchain(uint32_t pruning_seed) { - uint8_t hf_version = m_hardfork->get_current_version(); - if (hf_version < 10) - { - MERROR("Most of the network will only be ready for pruned blockchains from v10, not pruning"); - return false; - } return m_db->prune_blockchain(pruning_seed); } //------------------------------------------------------------------ @@ -3822,6 +3945,8 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti LOG_PRINT_L3("Blockchain::" << __func__); + m_db->block_txn_start(false); + // when we reach this, the last hf version is not yet written to the db const uint64_t db_height = m_db->height(); const uint8_t hf_version = get_current_hard_fork_version(); @@ -3884,6 +4009,10 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti if (long_term_effective_median_block_weight) *long_term_effective_median_block_weight = m_long_term_effective_median_block_weight; + m_db->add_max_block_size(m_current_block_cumul_weight_limit); + + m_db->block_txn_stop(); + return true; } //------------------------------------------------------------------ @@ -4282,8 +4411,9 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete for (unsigned int j = 0; j < batches; j++, ++blockidx) { block &block = blocks[blockidx]; + crypto::hash block_hash; - if (!parse_and_validate_block_from_blob(it->block, block)) + if (!parse_and_validate_block_from_blob(it->block, block, block_hash)) return false; // check first block and skip all blocks if its not chained properly @@ -4296,7 +4426,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete return true; } } - if (have_block(get_block_hash(block))) + if (have_block(block_hash)) blocks_exist = true; std::advance(it, 1); @@ -4306,11 +4436,12 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete for (unsigned i = 0; i < extra && !blocks_exist; i++, blockidx++) { block &block = blocks[blockidx]; + crypto::hash block_hash; - if (!parse_and_validate_block_from_blob(it->block, block)) + if (!parse_and_validate_block_from_blob(it->block, block, block_hash)) return false; - if (have_block(get_block_hash(block))) + if (have_block(block_hash)) blocks_exist = true; std::advance(it, 1); @@ -4838,13 +4969,14 @@ void Blockchain::invalidate_block_template_cache() m_btc_valid = false; } -void Blockchain::cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t expected_reward, uint64_t pool_cookie) +void Blockchain::cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t height, uint64_t expected_reward, uint64_t pool_cookie) { MDEBUG("Setting block template cache"); m_btc = b; m_btc_address = address; m_btc_nonce = nonce; m_btc_difficulty = diff; + m_btc_height = height; m_btc_expected_reward = expected_reward; m_btc_pool_cookie = pool_cookie; m_btc_valid = true; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 3b8169764..3588bbd1b 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -336,6 +336,7 @@ namespace cryptonote * @brief creates a new block to mine against * * @param b return-by-reference block to be filled in + * @param from_block optional block hash to start mining from (main chain tip if NULL) * @param miner_address address new coins for the block will go to * @param di return-by-reference tells the miner what the difficulty target is * @param height return-by-reference tells the miner what height it's mining against @@ -345,6 +346,7 @@ namespace cryptonote * @return true if block template filled in successfully, else false */ bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); + bool create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); /** * @brief checks if a block is known about with a given hash @@ -653,7 +655,7 @@ namespace cryptonote * * @return the difficulty */ - uint64_t block_difficulty(uint64_t i) const; + difficulty_type block_difficulty(uint64_t i) const; /** * @brief gets blocks based on a list of block hashes @@ -1060,6 +1062,8 @@ namespace cryptonote uint64_t m_timestamps_and_difficulties_height; uint64_t m_long_term_block_weights_window; uint64_t m_long_term_effective_median_block_weight; + mutable crypto::hash m_long_term_block_weights_cache_tip_hash; + mutable std::vector<uint64_t> m_long_term_block_weights_cache; epee::critical_section m_difficulty_lock; crypto::hash m_difficulty_for_next_block_top_hash; @@ -1092,6 +1096,7 @@ namespace cryptonote account_public_address m_btc_address; blobdata m_btc_nonce; difficulty_type m_btc_difficulty; + uint64_t m_btc_height; uint64_t m_btc_pool_cookie; uint64_t m_btc_expected_reward; bool m_btc_valid; @@ -1177,7 +1182,7 @@ namespace cryptonote * * @return false if the reorganization fails, otherwise true */ - bool switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::iterator>& alt_chain, bool discard_disconnected_chain); + bool switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::const_iterator>& alt_chain, bool discard_disconnected_chain); /** * @brief removes the most recent block from the blockchain @@ -1231,6 +1236,18 @@ namespace cryptonote bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); /** + * @brief builds a list of blocks connecting a block to the main chain + * + * @param prev_id the block hash of the tip of the alt chain + * @param alt_chain the chain to be added to + * @param timestamps returns the timestamps of previous blocks + * @param bvc the block verification context for error return + * + * @return true on success, false otherwise + */ + bool build_alt_chain(const crypto::hash &prev_id, std::list<blocks_ext_by_hash::const_iterator>& alt_chain, std::vector<uint64_t> ×tamps, block_verification_context& bvc) const; + + /** * @brief gets the difficulty requirement for a new block on an alternate chain * * @param alt_chain the chain to be added to @@ -1238,7 +1255,7 @@ namespace cryptonote * * @return the difficulty requirement */ - difficulty_type get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei) const; + difficulty_type get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::const_iterator>& alt_chain, block_extended_info& bei) const; /** * @brief sanity checks a miner transaction before validating an entire block @@ -1398,7 +1415,7 @@ namespace cryptonote * * @return true unless start_height is greater than the current blockchain height */ - bool complete_timestamps_vector(uint64_t start_height, std::vector<uint64_t>& timestamps); + bool complete_timestamps_vector(uint64_t start_height, std::vector<uint64_t>& timestamps) const; /** * @brief calculate the block weight limit for the next block to be added @@ -1462,6 +1479,6 @@ namespace cryptonote * * At some point, may be used to push an update to miners */ - void cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t expected_reward, uint64_t pool_cookie); + void cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t height, uint64_t expected_reward, uint64_t pool_cookie); }; } // namespace cryptonote diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 387203cc0..91dea4982 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -62,6 +62,9 @@ DISABLE_VS_WARNINGS(4355) #define BAD_SEMANTICS_TXES_MAX_SIZE 100 +// basically at least how many bytes the block itself serializes to without the miner tx +#define BLOCK_SIZE_SANITY_LEEWAY 100 + namespace cryptonote { const command_line::arg_descriptor<bool, false> arg_testnet_on = { @@ -1265,6 +1268,11 @@ namespace cryptonote return m_blockchain_storage.create_block_template(b, adr, diffic, height, expected_reward, ex_nonce); } //----------------------------------------------------------------------------------------------- + bool core::get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) + { + return m_blockchain_storage.create_block_template(b, prev_block, adr, diffic, height, expected_reward, ex_nonce); + } + //----------------------------------------------------------------------------------------------- bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const { return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp); @@ -1318,9 +1326,9 @@ namespace cryptonote return bce; } //----------------------------------------------------------------------------------------------- - bool core::handle_block_found(block& b) + bool core::handle_block_found(block& b, block_verification_context &bvc) { - block_verification_context bvc = boost::value_initialized<block_verification_context>(); + bvc = boost::value_initialized<block_verification_context>(); m_miner.pause(); std::vector<block_complete_entry> blocks; try @@ -1370,7 +1378,7 @@ namespace cryptonote m_pprotocol->relay_block(arg, exclude_context); } - return bvc.m_added_to_main_chain; + return true; } //----------------------------------------------------------------------------------------------- void core::on_synchronized() @@ -1417,22 +1425,26 @@ namespace cryptonote { TRY_ENTRY(); - // load json & DNS checkpoints every 10min/hour respectively, - // and verify them with respect to what blocks we already have - CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); - bvc = boost::value_initialized<block_verification_context>(); - if(block_blob.size() > get_max_block_size()) + + if (!check_incoming_block_size(block_blob)) { - LOG_PRINT_L1("WRONG BLOCK BLOB, too big size " << block_blob.size() << ", rejected"); bvc.m_verifivation_failed = true; return false; } + if (((size_t)-1) <= 0xffffffff && block_blob.size() >= 0x3fffffff) + MWARNING("This block's size is " << block_blob.size() << ", closing on the 32 bit limit"); + + // load json & DNS checkpoints every 10min/hour respectively, + // and verify them with respect to what blocks we already have + CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); + block lb; if (!b) { - if(!parse_and_validate_block_from_blob(block_blob, lb)) + crypto::hash block_hash; + if(!parse_and_validate_block_from_blob(block_blob, lb, block_hash)) { LOG_PRINT_L1("Failed to parse and validate new block"); bvc.m_verifivation_failed = true; @@ -1452,9 +1464,13 @@ namespace cryptonote // block_blob bool core::check_incoming_block_size(const blobdata& block_blob) const { - if(block_blob.size() > get_max_block_size()) + // note: we assume block weight is always >= block blob size, so we check incoming + // blob size against the block weight limit, which acts as a sanity check without + // having to parse/weigh first; in fact, since the block blob is the block header + // plus the tx hashes, the weight will typically be much larger than the blob size + if(block_blob.size() > m_blockchain_storage.get_current_cumulative_block_weight_limit() + BLOCK_SIZE_SANITY_LEEWAY) { - LOG_PRINT_L1("WRONG BLOCK BLOB, too big size " << block_blob.size() << ", rejected"); + LOG_PRINT_L1("WRONG BLOCK BLOB, sanity check failed on size " << block_blob.size() << ", rejected"); return false; } return true; @@ -1784,12 +1800,28 @@ namespace cryptonote return f; } //----------------------------------------------------------------------------------------------- - static double probability(unsigned int blocks, unsigned int expected) + static double probability1(unsigned int blocks, unsigned int expected) { // https://www.umass.edu/wsp/resources/poisson/#computing return pow(expected, blocks) / (factorial(blocks) * exp(expected)); } //----------------------------------------------------------------------------------------------- + static double probability(unsigned int blocks, unsigned int expected) + { + double p = 0.0; + if (blocks <= expected) + { + for (unsigned int b = 0; b <= blocks; ++b) + p += probability1(b, expected); + } + else if (blocks > expected) + { + for (unsigned int b = blocks; b <= expected * 3 /* close enough */; ++b) + p += probability1(b, expected); + } + return p; + } + //----------------------------------------------------------------------------------------------- bool core::check_block_rate() { if (m_offline || m_target_blockchain_height > get_current_blockchain_height()) diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 356265dd6..2fcf26a17 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -195,10 +195,11 @@ namespace cryptonote * the network. * * @param b the block found + * @param bvc returns the block verification flags * * @return true if the block was added to the main chain, otherwise false */ - virtual bool handle_block_found( block& b); + virtual bool handle_block_found(block& b, block_verification_context &bvc); /** * @copydoc Blockchain::create_block_template @@ -206,6 +207,7 @@ namespace cryptonote * @note see Blockchain::create_block_template */ virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); + virtual bool get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); /** * @brief called when a transaction is relayed diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index cb1561c2d..d38aa7474 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -104,6 +104,13 @@ namespace cryptonote std::vector<rct::key> &amount_keys, crypto::public_key &out_eph_public_key) ; + bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, + const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, + const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, + std::vector<crypto::public_key> &additional_tx_public_keys, + std::vector<rct::key> &amount_keys, + crypto::public_key &out_eph_public_key) ; + bool generate_genesis_block( block& bl , std::string const & genesis_tx diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index d582e3e9c..3083a5b4c 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -34,6 +34,7 @@ #include "serialization/keyvalue_serialization.h" #include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/blobdatatype.h" + namespace cryptonote { @@ -208,6 +209,7 @@ namespace cryptonote { uint64_t current_height; uint64_t cumulative_difficulty; + uint64_t cumulative_difficulty_top64; crypto::hash top_id; uint8_t top_version; uint32_t pruning_seed; @@ -215,6 +217,7 @@ namespace cryptonote BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(current_height) KV_SERIALIZE(cumulative_difficulty) + KV_SERIALIZE(cumulative_difficulty_top64) KV_SERIALIZE_VAL_POD_AS_BLOB(top_id) KV_SERIALIZE_OPT(top_version, (uint8_t)0) KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) @@ -245,12 +248,14 @@ namespace cryptonote uint64_t start_height; uint64_t total_height; uint64_t cumulative_difficulty; + uint64_t cumulative_difficulty_top64; std::vector<crypto::hash> m_block_ids; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(start_height) KV_SERIALIZE(total_height) KV_SERIALIZE(cumulative_difficulty) + KV_SERIALIZE(cumulative_difficulty_top64) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids) END_KV_SERIALIZE_MAP() }; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index b33867e8b..b7a50783a 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -48,6 +48,17 @@ #define MONERO_DEFAULT_LOG_CATEGORY "net.cn" #define MLOG_P2P_MESSAGE(x) MCINFO("net.p2p.msg", context << x) +#define MLOGIF_P2P_MESSAGE(init, test, x) \ + do { \ + const auto level = el::Level::Info; \ + const char *cat = "net.p2p.msg"; \ + if (ELPP->vRegistry()->allowed(level, cat)) { \ + init; \ + if (test) \ + el::base::Writer(level, __FILE__, __LINE__, ELPP_FUNC, el::base::DispatchAction::NormalLog).construct(cat) << x; \ + } \ + } while(0) + #define MLOG_PEER_STATE(x) \ MCINFO(MONERO_DEFAULT_LOG_CATEGORY, context << "[" << epee::string_tools::to_string_hex(context.m_pruning_seed) << "] state: " << x << " in state " << cryptonote::get_protocol_state_string(context.m_state)) @@ -398,7 +409,9 @@ namespace cryptonote { m_core.get_blockchain_top(hshd.current_height, hshd.top_id); hshd.top_version = m_core.get_ideal_hard_fork_version(hshd.current_height); - hshd.cumulative_difficulty = m_core.get_block_cumulative_difficulty(hshd.current_height); + difficulty_type wide_cumulative_difficulty = m_core.get_block_cumulative_difficulty(hshd.current_height); + hshd.cumulative_difficulty = (wide_cumulative_difficulty << 64 >> 64).convert_to<uint64_t>(); + hshd.cumulative_difficulty_top64 = (wide_cumulative_difficulty >> 64).convert_to<uint64_t>(); hshd.current_height +=1; hshd.pruning_seed = m_core.get_blockchain_pruning_seed(); return true; @@ -416,7 +429,7 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context) { - MLOG_P2P_MESSAGE("Received NOTIFY_NEW_BLOCK (" << arg.b.txs.size() << " txes)"); + MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks @@ -486,7 +499,7 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_notify_new_fluffy_block(int command, NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& context) { - MLOG_P2P_MESSAGE("Received NOTIFY_NEW_FLUFFY_BLOCK (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); + MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_FLUFFY_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks @@ -856,6 +869,9 @@ namespace cryptonote int t_cryptonote_protocol_handler<t_core>::handle_notify_new_transactions(int command, NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& context) { MLOG_P2P_MESSAGE("Received NOTIFY_NEW_TRANSACTIONS (" << arg.txs.size() << " txes)"); + for (const auto &blob: arg.txs) + MLOGIF_P2P_MESSAGE(cryptonote::transaction tx; crypto::hash hash; bool ret = cryptonote::parse_and_validate_tx_from_blob(blob, tx, hash);, ret, "Including transaction " << hash); + if(context.m_state != cryptonote_connection_context::state_normal) return 1; @@ -993,7 +1009,8 @@ namespace cryptonote return 1; } - if(!parse_and_validate_block_from_blob(block_entry.block, b)) + crypto::hash block_hash; + if(!parse_and_validate_block_from_blob(block_entry.block, b, block_hash)) { LOG_ERROR_CCONTEXT("sent wrong block: failed to parse and validate block: " << epee::string_tools::buff_to_hex_nodelimer(block_entry.block) << ", dropping connection"); @@ -1012,7 +1029,6 @@ namespace cryptonote if (start_height == std::numeric_limits<uint64_t>::max()) start_height = boost::get<txin_gen>(b.miner_tx.vin[0]).height; - const crypto::hash block_hash = get_block_hash(b); auto req_it = context.m_requested_objects.find(block_hash); if(req_it == context.m_requested_objects.end()) { @@ -1119,13 +1135,13 @@ namespace cryptonote << ", we need " << previous_height); block new_block; - if (!parse_and_validate_block_from_blob(blocks.back().block, new_block)) + crypto::hash last_block_hash; + if (!parse_and_validate_block_from_blob(blocks.back().block, new_block, last_block_hash)) { MERROR(context << "Failed to parse block, but it should already have been parsed"); m_block_queue.remove_spans(span_connection_id, start_height); continue; } - const crypto::hash last_block_hash = cryptonote::get_block_hash(new_block); if (m_core.have_block(last_block_hash)) { const uint64_t subchain_height = start_height + blocks.size(); diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index b324ab99d..17b945c9a 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -127,6 +127,13 @@ bool t_command_parser_executor::print_connections(const std::vector<std::string> return m_executor.print_connections(); } +bool t_command_parser_executor::print_net_stats(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + + return m_executor.print_net_stats(); +} + bool t_command_parser_executor::print_blockchain_info(const std::vector<std::string>& args) { if(!args.size()) diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index bec6e4522..098018642 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -148,6 +148,8 @@ public: bool prune_blockchain(const std::vector<std::string>& args); bool check_blockchain_pruning(const std::vector<std::string>& args); + + bool print_net_stats(const std::vector<std::string>& args); }; } // namespace daemonize diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 94e4a8bf1..69ad6ff10 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -78,6 +78,11 @@ t_command_server::t_command_server( , "Print the current connections." ); m_command_lookup.set_handler( + "print_net_stats" + , std::bind(&t_command_parser_executor::print_net_stats, &m_parser, p::_1) + , "Print network statistics." + ); + m_command_lookup.set_handler( "print_bc" , std::bind(&t_command_parser_executor::print_blockchain_info, &m_parser, p::_1) , "print_bc <begin_height> [<end_height>]" diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 3d1d893ea..531c080de 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -115,6 +115,7 @@ t_daemon::t_daemon(t_daemon && other) { mp_internals = std::move(other.mp_internals); other.mp_internals.reset(nullptr); + public_rpc_port = other.public_rpc_port; } } @@ -125,6 +126,7 @@ t_daemon & t_daemon::operator=(t_daemon && other) { mp_internals = std::move(other.mp_internals); other.mp_internals.reset(nullptr); + public_rpc_port = other.public_rpc_port; } return *this; } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 4ee67f571..5901be662 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -551,11 +551,12 @@ bool t_rpc_command_executor::mining_status() { tools::msg_writer() << " Ignore battery: " << (mres.bg_ignore_battery ? "yes" : "no"); } - if (!mining_busy && mres.active) + if (!mining_busy && mres.active && mres.speed > 0 && mres.block_target > 0 && mres.difficulty > 0) { - uint64_t daily = 86400ull / mres.block_target * mres.block_reward; - uint64_t monthly = 86400ull / mres.block_target * 30.5 * mres.block_reward; - uint64_t yearly = 86400ull / mres.block_target * 356 * mres.block_reward; + double ratio = mres.speed * mres.block_target / mres.difficulty; + uint64_t daily = 86400ull / mres.block_target * mres.block_reward * ratio; + uint64_t monthly = 86400ull / mres.block_target * 30.5 * mres.block_reward * ratio; + uint64_t yearly = 86400ull / mres.block_target * 356 * mres.block_reward * ratio; tools::msg_writer() << "Expected: " << cryptonote::print_money(daily) << " monero daily, " << cryptonote::print_money(monthly) << " monero monthly, " << cryptonote::print_money(yearly) << " yearly"; } @@ -627,6 +628,66 @@ bool t_rpc_command_executor::print_connections() { return true; } +bool t_rpc_command_executor::print_net_stats() +{ + cryptonote::COMMAND_RPC_GET_NET_STATS::request net_stats_req; + cryptonote::COMMAND_RPC_GET_NET_STATS::response net_stats_res; + cryptonote::COMMAND_RPC_GET_LIMIT::request limit_req; + cryptonote::COMMAND_RPC_GET_LIMIT::response limit_res; + + std::string fail_message = "Unsuccessful"; + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(net_stats_req, net_stats_res, "get_net_stats", fail_message.c_str())) + { + return true; + } + if (!m_rpc_client->json_rpc_request(limit_req, limit_res, "get_limit", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_net_stats(net_stats_req, net_stats_res) || net_stats_res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, net_stats_res.status); + return true; + } + if (!m_rpc_server->on_get_limit(limit_req, limit_res) || limit_res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, limit_res.status); + return true; + } + } + + uint64_t seconds = (uint64_t)time(NULL) - net_stats_res.start_time; + uint64_t average = seconds > 0 ? net_stats_res.total_bytes_in / seconds : 0; + uint64_t limit = limit_res.limit_down * 1024; // convert to bytes, as limits are always kB/s + double percent = (double)average / (double)limit * 100.0; + tools::success_msg_writer() << boost::format("Received %u bytes (%s) in %u packets, average %s/s = %.2f%% of the limit of %s/s") + % net_stats_res.total_bytes_in + % tools::get_human_readable_bytes(net_stats_res.total_bytes_in) + % net_stats_res.total_packets_in + % tools::get_human_readable_bytes(average) + % percent + % tools::get_human_readable_bytes(limit); + + average = seconds > 0 ? net_stats_res.total_bytes_out / seconds : 0; + limit = limit_res.limit_up * 1024; + percent = (double)average / (double)limit * 100.0; + tools::success_msg_writer() << boost::format("Sent %u bytes (%s) in %u packets, average %s/s = %.2f%% of the limit of %s/s") + % net_stats_res.total_bytes_out + % tools::get_human_readable_bytes(net_stats_res.total_bytes_out) + % net_stats_res.total_packets_out + % tools::get_human_readable_bytes(average) + % percent + % tools::get_human_readable_bytes(limit); + + return true; +} + bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, uint64_t end_block_index) { cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request req; cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response res; @@ -2175,7 +2236,7 @@ bool t_rpc_command_executor::check_blockchain_pruning() if (res.pruning_seed) { - tools::success_msg_writer() << "Blockchain pruning checked"; + tools::success_msg_writer() << "Blockchain is pruned"; } else { diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 423132b79..3c2686b3f 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -160,6 +160,8 @@ public: bool prune_blockchain(); bool check_blockchain_pruning(); + + bool print_net_stats(); }; } // namespace daemonize diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt index ffa1458b0..0b0686f61 100644 --- a/src/device/CMakeLists.txt +++ b/src/device/CMakeLists.txt @@ -75,4 +75,6 @@ target_link_libraries(device ${OPENSSL_CRYPTO_LIBRARIES} ${Boost_SERIALIZATION_LIBRARY} PRIVATE + version + ${Blocks} ${EXTRA_LIBRARIES}) diff --git a/src/device/device.hpp b/src/device/device.hpp index 65b38361b..866e2c676 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -27,21 +27,6 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // - -/* Note about debug: - * To debug Device you can def the following : - * #define DEBUG_HWDEVICE - * Activate debug mechanism: - * - Add more trace - * - All computation done by device are checked by default device. - * Required IODUMMYCRYPT_HWDEVICE or IONOCRYPT_HWDEVICE for fully working - * #define IODUMMYCRYPT_HWDEVICE 1 - * - It assumes sensitive data encryption is is off on device side. a XOR with 0x55. This allow Ledger Class to make check on clear value - * #define IONOCRYPT_HWDEVICE 1 - * - It assumes sensitive data encryption is off on device side. - */ - - #pragma once #include "crypto/crypto.h" @@ -91,6 +76,7 @@ namespace hw { class i_device_callback { public: virtual void on_button_request(uint64_t code=0) {} + virtual void on_button_pressed() {} virtual boost::optional<epee::wipeable_string> on_pin_request() { return boost::none; } virtual boost::optional<epee::wipeable_string> on_passphrase_request(bool on_device) { return boost::none; } virtual void on_progress(const device_progress& event) {} @@ -211,6 +197,10 @@ namespace hw { /* TRANSACTION */ /* ======================================================================= */ + virtual void generate_tx_proof(const crypto::hash &prefix_hash, + const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r, + crypto::signature &sig) = 0; + virtual bool open_tx(crypto::secret_key &tx_key) = 0; virtual bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) = 0; @@ -220,6 +210,8 @@ namespace hw { return encrypt_payment_id(payment_id, public_key, secret_key); } + virtual rct::key genCommitmentMask(const rct::key &amount_key) = 0; + virtual bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_amount) = 0; virtual bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_amount) = 0; diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp index 999fbc22f..dc06ce237 100644 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -37,7 +37,6 @@ #include "cryptonote_core/cryptonote_tx_utils.h" #include "ringct/rctOps.h" -#include "log.hpp" #define ENCRYPTED_PAYMENT_ID_TAIL 0x8d #define CHACHA8_KEY_TAIL 0x8c @@ -273,6 +272,11 @@ namespace hw { /* ======================================================================= */ /* TRANSACTION */ /* ======================================================================= */ + void device_default::generate_tx_proof(const crypto::hash &prefix_hash, + const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r, + crypto::signature &sig) { + crypto::generate_tx_proof(prefix_hash, R, A, B, D, r, sig); + } bool device_default::open_tx(crypto::secret_key &tx_key) { cryptonote::keypair txkey = cryptonote::keypair::generate(*this); @@ -349,6 +353,10 @@ namespace hw { return true; } + rct::key device_default::genCommitmentMask(const rct::key &amount_key) { + return rct::genCommitmentMask(amount_key); + } + bool device_default::ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_amount) { rct::ecdhEncode(unmasked, sharedSec, short_amount); return true; diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp index 90d39495b..5252d4129 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -107,10 +107,16 @@ namespace hw { /* TRANSACTION */ /* ======================================================================= */ + void generate_tx_proof(const crypto::hash &prefix_hash, + const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r, + crypto::signature &sig) override; + bool open_tx(crypto::secret_key &tx_key) override; bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override; + rct::key genCommitmentMask(const rct::key &amount_key) override; + bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_amount) override; bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_amount) override; diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp index f07e0eaae..721bed9ca 100644 --- a/src/device/device_io_hid.cpp +++ b/src/device/device_io_hid.cpp @@ -85,7 +85,18 @@ namespace hw { 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); + if (!this->connect(p->vid, p->pid, p->interface_number, p->usage_page)) { + ASSERT_X(false, "No device found"); + } + } + + void device_io_hid::connect(const std::vector<hid_conn_params> &hcpV) { + for (auto p: hcpV) { + if (this->connect(p.vid, p.pid, p.interface_number, p.usage_page)) { + return; + } + } + ASSERT_X(false, "No device found"); } hid_device_info *device_io_hid::find_device(hid_device_info *devices_list, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page) { @@ -124,14 +135,17 @@ namespace hw { 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 *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)); + if (!hwdev_info_list) { + MDEBUG("Unable to enumerate device "+std::to_string(vid)+":"+std::to_string(vid)+ ": "+ safe_hid_error(this->usb_device)); + return NULL; + } hwdev = NULL; if (hid_device_info *device = find_device(hwdev_info_list, interface_number, usage_page)) { hwdev = hid_open_path(device->path); @@ -141,6 +155,7 @@ namespace hw { this->usb_vid = vid; this->usb_pid = pid; this->usb_device = hwdev; + return hwdev; } diff --git a/src/device/device_io_hid.hpp b/src/device/device_io_hid.hpp index ed22058d6..96cb8d993 100644 --- a/src/device/device_io_hid.hpp +++ b/src/device/device_io_hid.hpp @@ -98,7 +98,8 @@ namespace hw { 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); + void connect(const std::vector<hid_conn_params> &conn); + hid_device *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, bool user_input); void disconnect(); diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 0f197272c..200370564 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -27,8 +27,8 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // +#include "version.h" #include "device_ledger.hpp" -#include "log.hpp" #include "ringct/rctOps.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/subaddress_index.h" @@ -173,6 +173,7 @@ namespace hw { #define INS_SET_SIGNATURE_MODE 0x72 #define INS_GET_ADDITIONAL_KEY 0x74 #define INS_STEALTH 0x76 + #define INS_GEN_COMMITMENT_MASK 0x77 #define INS_BLIND 0x78 #define INS_UNBLIND 0x7A #define INS_GEN_TXOUT_KEYS 0x7B @@ -180,6 +181,7 @@ namespace hw { #define INS_MLSAG 0x7E #define INS_CLOSE_TX 0x80 + #define INS_GET_TX_PROOF 0xA0 #define INS_GET_RESPONSE 0xc0 @@ -302,8 +304,24 @@ namespace hw { } bool device_ledger::reset() { - send_simple(INS_RESET); - return true; + reset_buffer(); + int offset = set_command_header_noopt(INS_RESET); + memmove(this->buffer_send+offset, MONERO_VERSION, strlen(MONERO_VERSION)); + offset += strlen(MONERO_VERSION); + this->buffer_send[4] = offset-5; + this->length_send = offset; + this->exchange(); + + ASSERT_X(this->length_recv>=3, "Communication error, less than three bytes received. Check your application version."); + + unsigned int device_version = 0; + device_version = VERSION(this->buffer_recv[0], this->buffer_recv[1], this->buffer_recv[2]); + + ASSERT_X (device_version >= MINIMAL_APP_VERSION, + "Unsupported device application version: " << VERSION_MAJOR(device_version)<<"."<<VERSION_MINOR(device_version)<<"."<<VERSION_MICRO(device_version) << + " At least " << MINIMAL_APP_VERSION_MAJOR<<"."<<MINIMAL_APP_VERSION_MINOR<<"."<<MINIMAL_APP_VERSION_MICRO<<" is required."); + + return true; } unsigned int device_ledger::exchange(unsigned int ok, unsigned int mask) { @@ -314,9 +332,9 @@ namespace hw { this->length_recv -= 2; this->sw = (this->buffer_recv[length_recv]<<8) | this->buffer_recv[length_recv+1]; + logRESP(); ASSERT_SW(this->sw,ok,msk); - logRESP(); return this->sw; } @@ -371,10 +389,15 @@ namespace hw { MDEBUG( "Device "<<this->id <<" HIDUSB inited"); return true; } + + static const std::vector<hw::io::hid_conn_params> known_devices { + {0x2c97, 0x0001, 0, 0xffa0}, + {0x2c97, 0x0004, 0, 0xffa0}, + }; bool device_ledger::connect(void) { this->disconnect(); - hw_device.connect(0x2c97, 0x0001, 0, 0xffa0); + hw_device.connect(known_devices); this->reset(); #ifdef DEBUG_HWDEVICE cryptonote::account_public_address pubkey; @@ -790,7 +813,11 @@ namespace hw { const crypto::secret_key a_x = hw::ledger::decrypt(a); const crypto::secret_key b_x = hw::ledger::decrypt(b); crypto::secret_key r_x; + rct::key aG_x; + log_hexbuffer("sc_secret_add: [[IN]] a ", (char*)a_x.data, 32); + log_hexbuffer("sc_secret_add: [[IN]] b ", (char*)b_x.data, 32); this->controle_device->sc_secret_add(r_x, a_x, b_x); + log_hexbuffer("sc_secret_add: [[OUT]] aG", (char*)r_x.data, 32); #endif int offset = set_command_header_noopt(INS_SECRET_KEY_ADD); @@ -825,6 +852,11 @@ namespace hw { #ifdef DEBUG_HWDEVICE crypto::public_key pub_x; crypto::secret_key sec_x; + crypto::secret_key recovery_key_x; + if (recover) { + recovery_key_x = hw::ledger::decrypt(recovery_key); + log_hexbuffer("generate_keys: [[IN]] pub", (char*)recovery_key_x.data, 32); + } #endif send_simple(INS_GENERATE_KEYPAIR); @@ -836,6 +868,9 @@ namespace hw { #ifdef DEBUG_HWDEVICE crypto::secret_key sec_clear = hw::ledger::decrypt(sec); sec_x = sec_clear; + log_hexbuffer("generate_keys: [[OUT]] pub", (char*)pub.data, 32); + log_hexbuffer("generate_keys: [[OUT]] sec", (char*)sec_clear.data, 32); + crypto::secret_key_to_public_key(sec_x,pub_x); hw::ledger::check32("generate_keys", "pub", pub_x.data, pub.data); #endif @@ -850,7 +885,7 @@ namespace hw { #ifdef DEBUG_HWDEVICE const crypto::public_key pub_x = pub; - const crypto::secret_key sec_x = hw::ledger::decrypt(sec); + const crypto::secret_key sec_x = (sec == rct::rct2sk(rct::I)) ? sec: hw::ledger::decrypt(sec); crypto::key_derivation derivation_x; log_hexbuffer("generate_key_derivation: [[IN]] pub ", pub_x.data, 32); log_hexbuffer("generate_key_derivation: [[IN]] sec ", sec_x.data, 32); @@ -866,7 +901,6 @@ namespace hw { assert(is_fake_view_key(sec)); r = crypto::generate_key_derivation(pub, this->viewkey, derivation); } else { - int offset = set_command_header_noopt(INS_GEN_KEY_DERIVATION); //pub memmove(this->buffer_send+offset, pub.data, 32); @@ -885,11 +919,11 @@ namespace hw { } #ifdef DEBUG_HWDEVICE crypto::key_derivation derivation_clear ; - if ((this->mode == TRANSACTION_PARSE) && has_view_key) { - derivation_clear = derivation; - }else { - derivation_clear = hw::ledger::decrypt(derivation); - } + if ((this->mode == TRANSACTION_PARSE) && has_view_key) { + derivation_clear = derivation; + } else { + derivation_clear = hw::ledger::decrypt(derivation); + } hw::ledger::check32("generate_key_derivation", "derivation", derivation_x.data, derivation_clear.data); #endif @@ -1050,7 +1084,7 @@ namespace hw { bool rc = this->controle_device->secret_key_to_public_key(sec_x, pub_x); log_hexbuffer("secret_key_to_public_key: [[OUT]] pub", pub_x.data, 32); if (!rc){ - log_message("secret_key_to_public_key", "secret_key rejected"); + log_message("FAIL secret_key_to_public_key", "secret_key rejected"); } #endif @@ -1112,6 +1146,75 @@ namespace hw { /* TRANSACTION */ /* ======================================================================= */ + void device_ledger::generate_tx_proof(const crypto::hash &prefix_hash, + const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r, + crypto::signature &sig) { + + AUTO_LOCK_CMD(); + + #ifdef DEBUG_HWDEVICE + const crypto::hash prefix_hash_x = prefix_hash; + const crypto::public_key R_x = R; + const crypto::public_key A_x = A; + const boost::optional<crypto::public_key> B_x = B; + const crypto::public_key D_x = D; + const crypto::secret_key r_x = hw::ledger::decrypt(r); + crypto::signature sig_x; + log_hexbuffer("generate_tx_proof: [[IN]] prefix_hash ", prefix_hash_x.data, 32); + log_hexbuffer("generate_tx_proof: [[IN]] R ", R_x.data, 32); + log_hexbuffer("generate_tx_proof: [[IN]] A ", A_x.data, 32); + if (B_x) { + log_hexbuffer("generate_tx_proof: [[IN]] B ", (*B_x).data, 32); + } + log_hexbuffer("generate_tx_proof: [[IN]] D ", D_x.data, 32); + log_hexbuffer("generate_tx_proof: [[IN]] r ", r_x.data, 32); + #endif + + + int offset = set_command_header(INS_GET_TX_PROOF); + //options + this->buffer_send[offset] = B?0x01:0x00; + offset += 1; + //prefix_hash + memmove(&this->buffer_send[offset], prefix_hash.data, 32); + offset += 32; + // R + memmove(&this->buffer_send[offset], R.data, 32); + offset += 32; + // A + memmove(&this->buffer_send[offset], A.data, 32); + offset += 32; + // B + if (B) { + memmove(&this->buffer_send[offset], (*B).data, 32); + } else { + memset(&this->buffer_send[offset], 0, 32); + } + offset += 32; + // D + memmove(&this->buffer_send[offset], D.data, 32); + offset += 32; + // r + memmove(&this->buffer_send[offset], r.data, 32); + offset += 32; + + this->buffer_send[4] = offset-5; + this->length_send = offset; + this->exchange(); + + memmove(sig.c.data, &this->buffer_recv[0], 32); + memmove(sig.r.data, &this->buffer_recv[32], 32); + #ifdef DEBUG_HWDEVICE + log_hexbuffer("GENERATE_TX_PROOF: **c** ", sig.c.data, sizeof( sig.c.data)); + log_hexbuffer("GENERATE_TX_PROOF: **r** ", sig.r.data, sizeof( sig.r.data)); + + this->controle_device->generate_tx_proof(prefix_hash_x, R_x, A_x, B_x, D_x, r_x, sig_x); + hw::ledger::check32("generate_tx_proof", "c", sig_x.c.data, sig.c.data); + hw::ledger::check32("generate_tx_proof", "r", sig_x.r.data, sig.r.data); + + #endif + } + bool device_ledger::open_tx(crypto::secret_key &tx_key) { AUTO_LOCK_CMD(); @@ -1130,7 +1233,11 @@ namespace hw { this->exchange(); memmove(tx_key.data, &this->buffer_recv[32], 32); - + #ifdef DEBUG_HWDEVICE + const crypto::secret_key r_x = hw::ledger::decrypt(tx_key); + log_hexbuffer("open_tx: [[OUT]] R ", (char*)&this->buffer_recv[0], 32); + log_hexbuffer("open_tx: [[OUT]] r ", r_x.data, 32); + #endif return true; } @@ -1141,7 +1248,11 @@ namespace hw { const crypto::public_key public_key_x = public_key; const crypto::secret_key secret_key_x = hw::ledger::decrypt(secret_key); crypto::hash8 payment_id_x = payment_id; + log_hexbuffer("encrypt_payment_id: [[IN]] payment_id ", payment_id_x.data, 32); + log_hexbuffer("encrypt_payment_id: [[IN]] public_key ", public_key_x.data, 32); + log_hexbuffer("encrypt_payment_id: [[IN]] secret_key ", secret_key_x.data, 32); this->controle_device->encrypt_payment_id(payment_id_x, public_key_x, secret_key_x); + log_hexbuffer("encrypt_payment_id: [[OUT]] payment_id ", payment_id_x.data, 32); #endif int offset = set_command_header_noopt(INS_STEALTH); @@ -1178,49 +1289,58 @@ namespace hw { #ifdef DEBUG_HWDEVICE const size_t &tx_version_x = tx_version; - const cryptonote::account_keys sender_account_keys_x = sender_account_keys; + const cryptonote::account_keys sender_account_keys_x = hw::ledger::decrypt(sender_account_keys); memmove((void*)sender_account_keys_x.m_view_secret_key.data, dbg_viewkey.data, 32); - const crypto::public_key &txkey_pub_x = txkey_pub; - const crypto::secret_key &tx_key_x = tx_key; - const cryptonote::tx_destination_entry &dst_entr_x = dst_entr; - const boost::optional<cryptonote::account_public_address> &change_addr_x = change_addr; - const size_t &output_index_x = output_index; - const bool &need_additional_txkeys_x = need_additional_txkeys; - const std::vector<crypto::secret_key> &additional_tx_keys_x = additional_tx_keys; + const crypto::public_key txkey_pub_x = txkey_pub; + const crypto::secret_key tx_key_x = hw::ledger::decrypt(tx_key); + const cryptonote::tx_destination_entry dst_entr_x = dst_entr; + const boost::optional<cryptonote::account_public_address> change_addr_x = change_addr; + const size_t output_index_x = output_index; + const bool need_additional_txkeys_x = need_additional_txkeys; + + std::vector<crypto::secret_key> additional_tx_keys_x; + for (const auto k: additional_tx_keys) { + additional_tx_keys_x.push_back(hw::ledger::decrypt(k)); + } + std::vector<crypto::public_key> additional_tx_public_keys_x; std::vector<rct::key> amount_keys_x; crypto::public_key out_eph_public_key_x; + + log_message ("generate_output_ephemeral_keys: [[IN]] tx_version", std::to_string(tx_version_x)); + //log_hexbuffer("generate_output_ephemeral_keys: [[IN]] sender_account_keys.view", sender_account_keys.m_sview_secret_key.data, 32); + //log_hexbuffer("generate_output_ephemeral_keys: [[IN]] sender_account_keys.spend", sender_account_keys.m_spend_secret_key.data, 32); + log_hexbuffer("generate_output_ephemeral_keys: [[IN]] txkey_pub", txkey_pub_x.data, 32); + log_hexbuffer("generate_output_ephemeral_keys: [[IN]] tx_key", tx_key_x.data, 32); + log_hexbuffer("generate_output_ephemeral_keys: [[IN]] dst_entr.view", dst_entr_x.addr.m_view_public_key.data, 32); + log_hexbuffer("generate_output_ephemeral_keys: [[IN]] dst_entr.spend", dst_entr_x.addr.m_spend_public_key.data, 32); + if (change_addr) { + log_hexbuffer("generate_output_ephemeral_keys: [[IN]] change_addr.view", (*change_addr_x).m_view_public_key.data, 32); + log_hexbuffer("generate_output_ephemeral_keys: [[IN]] change_addr.spend", (*change_addr_x).m_spend_public_key.data, 32); + } + log_message ("generate_output_ephemeral_keys: [[IN]] output_index", std::to_string(output_index_x)); + log_message ("generate_output_ephemeral_keys: [[IN]] need_additional_txkeys", std::to_string(need_additional_txkeys_x)); + if(need_additional_txkeys_x) { + log_hexbuffer("generate_output_ephemeral_keys: [[IN]] additional_tx_keys[oi]", additional_tx_keys_x[output_index].data, 32); + } this->controle_device->generate_output_ephemeral_keys(tx_version_x, sender_account_keys_x, txkey_pub_x, tx_key_x, dst_entr_x, change_addr_x, output_index_x, need_additional_txkeys_x, additional_tx_keys_x, additional_tx_public_keys_x, amount_keys_x, out_eph_public_key_x); + if(need_additional_txkeys_x) { + log_hexbuffer("additional_tx_public_keys_x: [[OUT]] additional_tx_public_keys_x", additional_tx_public_keys_x.back().data, 32); + } + log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] amount_keys ", (char*)amount_keys_x.back().bytes, 32); + log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] out_eph_public_key ", out_eph_public_key_x.data, 32); #endif + ASSERT_X(tx_version > 1, "TX version not supported"<<tx_version); + // make additional tx pubkey if necessary cryptonote::keypair additional_txkey; if (need_additional_txkeys) { additional_txkey.sec = additional_tx_keys[output_index]; } - //compute derivation, out_eph_public_key, and amount key in one shot on device, to ensure checkable link - const crypto::secret_key *sec; - bool is_change; - - if (change_addr && dst_entr.addr == *change_addr) - { - // sending change to yourself; derivation = a*R - is_change = true; - sec = &sender_account_keys.m_view_secret_key; - } - else - { - is_change = false; - if (dst_entr.is_subaddress && need_additional_txkeys) { - sec = &additional_txkey.sec; - } else { - sec = &tx_key; - } - } - int offset = set_command_header_noopt(INS_GEN_TXOUT_KEYS); //tx_version this->buffer_send[offset+0] = tx_version>>24; @@ -1228,8 +1348,11 @@ namespace hw { this->buffer_send[offset+2] = tx_version>>8; this->buffer_send[offset+3] = tx_version>>0; offset += 4; - //tx_sec - memmove(&this->buffer_send[offset], sec->data, 32); + //tx_key + memmove(&this->buffer_send[offset], tx_key.data, 32); + offset += 32; + //txkey_pub + memmove(&this->buffer_send[offset], txkey_pub.data, 32); offset += 32; //Aout memmove(&this->buffer_send[offset], dst_entr.addr.m_view_public_key.data, 32); @@ -1244,6 +1367,7 @@ namespace hw { this->buffer_send[offset+3] = output_index>>0; offset += 4; //is_change, + bool is_change = (change_addr && dst_entr.addr == *change_addr); this->buffer_send[offset] = is_change; offset++; //is_subaddress @@ -1252,6 +1376,13 @@ namespace hw { //need_additional_key this->buffer_send[offset] = need_additional_txkeys; offset++; + //additional_tx_key + if (need_additional_txkeys) { + memmove(&this->buffer_send[offset], additional_txkey.sec.data, 32); + } else { + memset(&this->buffer_send[offset], 0, 32); + } + offset += 32; this->buffer_send[4] = offset-5; this->length_send = offset; @@ -1259,15 +1390,8 @@ namespace hw { offset = 0; unsigned int recv_len = this->length_recv; - if (need_additional_txkeys) - { - ASSERT_X(recv_len>=32, "Not enought data from device"); - memmove(additional_txkey.pub.data, &this->buffer_recv[offset], 32); - additional_tx_public_keys.push_back(additional_txkey.pub); - offset += 32; - recv_len -= 32; - } - if (tx_version > 1) + + //if (tx_version > 1) { ASSERT_X(recv_len>=32, "Not enought data from device"); crypto::secret_key scalar1; @@ -1279,6 +1403,16 @@ namespace hw { ASSERT_X(recv_len>=32, "Not enought data from device"); memmove(out_eph_public_key.data, &this->buffer_recv[offset], 32); recv_len -= 32; + offset += 32; + + if (need_additional_txkeys) + { + ASSERT_X(recv_len>=32, "Not enought data from device"); + memmove(additional_txkey.pub.data, &this->buffer_recv[offset], 32); + additional_tx_public_keys.push_back(additional_txkey.pub); + offset += 32; + recv_len -= 32; + } // add ABPkeys this->add_output_key_mapping(dst_entr.addr.m_view_public_key, dst_entr.addr.m_spend_public_key, dst_entr.is_subaddress, is_change, @@ -1286,9 +1420,10 @@ namespace hw { amount_keys.back(), out_eph_public_key); #ifdef DEBUG_HWDEVICE + log_hexbuffer("generate_output_ephemeral_keys: clear amount_key", (const char*)hw::ledger::decrypt(amount_keys.back()).bytes, 32); hw::ledger::check32("generate_output_ephemeral_keys", "amount_key", (const char*)amount_keys_x.back().bytes, (const char*)hw::ledger::decrypt(amount_keys.back()).bytes); if (need_additional_txkeys) { - hw::ledger::check32("generate_output_ephemeral_keys", "additional_tx_key", additional_tx_keys_x.back().data, additional_tx_keys.back().data); + hw::ledger::check32("generate_output_ephemeral_keys", "additional_tx_key", additional_tx_public_keys_x.back().data, additional_tx_public_keys.back().data); } hw::ledger::check32("generate_output_ephemeral_keys", "out_eph_public_key", out_eph_public_key_x.data, out_eph_public_key.data); #endif @@ -1303,6 +1438,32 @@ namespace hw { return true; } + rct::key device_ledger::genCommitmentMask(const rct::key &AKout) { + #ifdef DEBUG_HWDEVICE + const rct::key AKout_x = hw::ledger::decrypt(AKout); + rct::key mask_x; + mask_x = this->controle_device->genCommitmentMask(AKout_x); + #endif + + rct::key mask; + int offset = set_command_header_noopt(INS_GEN_COMMITMENT_MASK); + // AKout + memmove(this->buffer_send+offset, AKout.bytes, 32); + offset += 32; + + this->buffer_send[4] = offset-5; + this->length_send = offset; + this->exchange(); + + memmove(mask.bytes, &this->buffer_recv[0], 32); + + #ifdef DEBUG_HWDEVICE + hw::ledger::check32("genCommitmentMask", "mask", (const char*)mask_x.bytes, (const char*)mask.bytes); + #endif + + return mask; + } + bool device_ledger::ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & AKout, bool short_amount) { AUTO_LOCK_CMD(); @@ -1334,6 +1495,7 @@ namespace hw { memmove(unmasked.mask.bytes, &this->buffer_recv[32], 32); #ifdef DEBUG_HWDEVICE + MDEBUG("ecdhEncode: Akout: "<<AKout_x); hw::ledger::check32("ecdhEncode", "amount", (char*)unmasked_x.amount.bytes, (char*)unmasked.amount.bytes); hw::ledger::check32("ecdhEncode", "mask", (char*)unmasked_x.mask.bytes, (char*)unmasked.mask.bytes); @@ -1374,6 +1536,7 @@ namespace hw { memmove(masked.mask.bytes, &this->buffer_recv[32], 32); #ifdef DEBUG_HWDEVICE + MDEBUG("ecdhEncode: Akout: "<<AKout_x); hw::ledger::check32("ecdhDecode", "amount", (char*)masked_x.amount.bytes, (char*)masked.amount.bytes); hw::ledger::check32("ecdhDecode", "mask", (char*)masked_x.mask.bytes,(char*) masked.mask.bytes); #endif diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index 252354e1c..d4d98ce4a 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -33,6 +33,7 @@ #include <cstddef> #include <string> #include "device.hpp" +#include "log.hpp" #include "device_io_hid.hpp" #include <boost/thread/mutex.hpp> #include <boost/thread/recursive_mutex.hpp> @@ -41,6 +42,18 @@ namespace hw { namespace ledger { + /* Minimal supported version */ + #define MINIMAL_APP_VERSION_MAJOR 1 + #define MINIMAL_APP_VERSION_MINOR 3 + #define MINIMAL_APP_VERSION_MICRO 1 + + #define VERSION(M,m,u) ((M)<<16|(m)<<8|(u)) + #define VERSION_MAJOR(v) (((v)>>16)&0xFF) + #define VERSION_MINOR(v) (((v)>>8)&0xFF) + #define VERSION_MICRO(v) (((v)>>0)&0xFF) + + #define MINIMAL_APP_VERSION VERSION(MINIMAL_APP_VERSION_MAJOR, MINIMAL_APP_VERSION_MINOR, MINIMAL_APP_VERSION_MICRO) + void register_all(std::map<std::string, std::unique_ptr<device>> ®istry); #ifdef WITH_DEVICE_LEDGER @@ -190,11 +203,16 @@ namespace hw { /* ======================================================================= */ /* TRANSACTION */ /* ======================================================================= */ - + void generate_tx_proof(const crypto::hash &prefix_hash, + const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r, + crypto::signature &sig) override; + bool open_tx(crypto::secret_key &tx_key) override; bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override; + rct::key genCommitmentMask(const rct::key &amount_key) override; + bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_format) override; bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_format) override; diff --git a/src/device/log.hpp b/src/device/log.hpp index fb7ba1fb0..bfe6e9edc 100644 --- a/src/device/log.hpp +++ b/src/device/log.hpp @@ -40,6 +40,19 @@ namespace hw { + /* Note about debug: + * To debug Device you can def the following : + * #define DEBUG_HWDEVICE + * Activate debug mechanism: + * - Add more trace + * - All computation done by device are checked by default device. + * Required IODUMMYCRYPT_HWDEVICE or IONOCRYPT_HWDEVICE for fully working + * #define IODUMMYCRYPT_HWDEVICE 1 + * - It assumes sensitive data encryption is is off on device side. a XOR with 0x55. This allow Ledger Class to make check on clear value + * #define IONOCRYPT_HWDEVICE 1 + * - It assumes sensitive data encryption is off on device side. + */ + 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 ); diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index b4a80cf2c..b1022dd9c 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -137,7 +137,7 @@ namespace trezor { } auto current_time = std::chrono::steady_clock::now(); - if (current_time - m_last_live_refresh_time <= std::chrono::seconds(20)) + if (current_time - m_last_live_refresh_time <= std::chrono::minutes(5)) { continue; } diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index f3d15c5e2..58abde1d1 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -43,7 +43,7 @@ namespace trezor { const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080}; - device_trezor_base::device_trezor_base(): m_callback(nullptr) { + device_trezor_base::device_trezor_base(): m_callback(nullptr), m_last_msg_type(messages::MessageType_Success) { #ifdef WITH_TREZOR_DEBUGGING m_debug = false; #endif @@ -275,6 +275,12 @@ namespace trezor { // Later if needed this generic message handler can be replaced by a pointer to // a protocol message handler which by default points to the device class which implements // the default handler. + + if (m_last_msg_type == messages::MessageType_ButtonRequest){ + on_button_pressed(); + } + m_last_msg_type = input.m_type; + switch(input.m_type){ case messages::MessageType_ButtonRequest: on_button_request(input, dynamic_cast<const messages::common::ButtonRequest*>(input.m_msg.get())); @@ -413,6 +419,11 @@ namespace trezor { resp = read_raw(); } + void device_trezor_base::on_button_pressed() + { + TREZOR_CALLBACK(on_button_pressed); + } + void device_trezor_base::on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg) { MDEBUG("on_pin_request"); diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 8c3c14b29..c106d2099 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -98,6 +98,7 @@ namespace trezor { std::shared_ptr<messages::management::Features> m_features; // features from the last device reset boost::optional<epee::wipeable_string> m_pin; boost::optional<epee::wipeable_string> m_passphrase; + messages::MessageType m_last_msg_type; cryptonote::network_type network_type; @@ -311,6 +312,7 @@ namespace trezor { // Protocol callbacks void on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg); + void on_button_pressed(); void on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg); void on_passphrase_request(GenericMessage & resp, const messages::common::PassphraseRequest * msg); void on_passphrase_state_request(GenericMessage & resp, const messages::common::PassphraseStateRequest * msg); diff --git a/src/lmdb/database.cpp b/src/lmdb/database.cpp index c6b244671..ccab1902a 100644 --- a/src/lmdb/database.cpp +++ b/src/lmdb/database.cpp @@ -46,7 +46,7 @@ namespace lmdb { namespace { - constexpr const std::size_t max_resize = 1 * 1024 * 1024 * 1024; // 1 GB + constexpr const mdb_size_t max_resize = 1 * 1024 * 1024 * 1024; // 1 GB void acquire_context(context& ctx) noexcept { while (ctx.lock.test_and_set()); @@ -136,7 +136,7 @@ namespace lmdb MDB_envinfo info{}; MONERO_LMDB_CHECK(mdb_env_info(handle(), &info)); - const std::size_t resize = std::min(info.me_mapsize, max_resize); + const mdb_size_t resize = std::min(info.me_mapsize, max_resize); const int err = mdb_env_set_mapsize(handle(), info.me_mapsize + resize); ctx.lock.clear(); if (err) diff --git a/src/lmdb/value_stream.cpp b/src/lmdb/value_stream.cpp index 1024deb06..604140e47 100644 --- a/src/lmdb/value_stream.cpp +++ b/src/lmdb/value_stream.cpp @@ -36,9 +36,9 @@ namespace lmdb { namespace stream { - std::size_t count(MDB_cursor* cur) + mdb_size_t count(MDB_cursor* cur) { - std::size_t out = 0; + mdb_size_t out = 0; if (cur) { const int rc = mdb_cursor_count(cur, &out); diff --git a/src/lmdb/value_stream.h b/src/lmdb/value_stream.h index c9977221f..01090aa67 100644 --- a/src/lmdb/value_stream.h +++ b/src/lmdb/value_stream.h @@ -43,7 +43,7 @@ namespace lmdb \throw std::system_error if unexpected LMDB error. \return 0 if `cur == nullptr`, otherwise count of values at current key. */ - std::size_t count(MDB_cursor* cur); + mdb_size_t count(MDB_cursor* cur); /*! Calls `mdb_cursor_get` and does some error checking. diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index 48c9ab1ba..2dd40cc9a 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -340,9 +340,7 @@ namespace crypto const size_t expected = len * 3 / 32; if (seed.size() == expected/2) { - dst += ' '; // if electrum 12-word seed, duplicate - dst += dst; // if electrum 12-word seed, duplicate - dst.pop_back(); // trailing space + dst.append(dst.data(), dst.size()); // if electrum 12-word seed, duplicate } } diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index ba6e79d3f..f0aef384f 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -502,7 +502,7 @@ namespace nodetool else { memcpy(&m_network_id, &::config::NETWORK_ID, 16); - if (m_exclusive_peers.empty()) + if (m_exclusive_peers.empty() && !m_offline) { // for each hostname in the seed nodes list, attempt to DNS resolve and // add the result addresses as seed nodes @@ -2236,11 +2236,10 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::set_rate_up_limit(const boost::program_options::variables_map& vm, int64_t limit) { - this->islimitup=true; + this->islimitup=(limit != -1) && (limit != default_limit_up); if (limit==-1) { limit=default_limit_up; - this->islimitup=false; } epee::net_utils::connection<epee::levin::async_protocol_handler<p2p_connection_context> >::set_rate_up_limit( limit ); @@ -2251,10 +2250,9 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::set_rate_down_limit(const boost::program_options::variables_map& vm, int64_t limit) { - this->islimitdown=true; + this->islimitdown=(limit != -1) && (limit != default_limit_down); if(limit==-1) { limit=default_limit_down; - this->islimitdown=false; } epee::net_utils::connection<epee::levin::async_protocol_handler<p2p_connection_context> >::set_rate_down_limit( limit ); MINFO("Set limit-down to " << limit << " kB/s"); diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp index e39ba16fd..b5499262f 100644 --- a/src/ringct/rctOps.cpp +++ b/src/ringct/rctOps.cpp @@ -408,10 +408,10 @@ namespace rct { return res; } - //Computes aL where L is the curve order - bool isInMainSubgroup(const key & a) { + //Computes lA where l is the curve order + bool isInMainSubgroup(const key & A) { ge_p3 p3; - return toPointCheckOrder(&p3, a.bytes); + return toPointCheckOrder(&p3, A.bytes); } //Curve addition / subtractions diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 25571238e..e877c13ce 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -79,12 +79,12 @@ namespace } namespace rct { - Bulletproof proveRangeBulletproof(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts, epee::span<const key> sk) + Bulletproof proveRangeBulletproof(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts, epee::span<const key> sk, hw::device &hwdev) { CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes"); masks.resize(amounts.size()); for (size_t i = 0; i < masks.size(); ++i) - masks[i] = genCommitmentMask(sk[i]); + masks[i] = hwdev.genCommitmentMask(sk[i]); Bulletproof proof = bulletproof_PROVE(amounts, masks); CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size"); C = proof.V; @@ -804,7 +804,7 @@ namespace rct { else { const epee::span<const key> keys{&amount_keys[0], amount_keys.size()}; - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys)); + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); #ifdef DBG CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); #endif @@ -833,7 +833,7 @@ namespace rct { else { const epee::span<const key> keys{&amount_keys[amounts_proved], batch_size}; - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys)); + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev)); #ifdef DBG CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); #endif diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 56b0361a7..71bfcc950 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -70,6 +70,13 @@ namespace { return (value + quantum - 1) / quantum * quantum; } + + void store_difficulty(cryptonote::difficulty_type difficulty, uint64_t &sdiff, std::string &swdiff, uint64_t &stop64) + { + sdiff = (difficulty << 64 >> 64).convert_to<uint64_t>(); + swdiff = difficulty.convert_to<std::string>(); + stop64 = (difficulty >> 64).convert_to<uint64_t>(); + } } namespace cryptonote @@ -84,7 +91,7 @@ namespace cryptonote command_line::add_arg(desc, arg_rpc_ssl); command_line::add_arg(desc, arg_rpc_ssl_private_key); command_line::add_arg(desc, arg_rpc_ssl_certificate); - command_line::add_arg(desc, arg_rpc_ssl_allowed_certificates); + command_line::add_arg(desc, arg_rpc_ssl_ca_certificates); command_line::add_arg(desc, arg_rpc_ssl_allowed_fingerprints); command_line::add_arg(desc, arg_rpc_ssl_allow_any_cert); command_line::add_arg(desc, arg_bootstrap_daemon_address); @@ -142,36 +149,38 @@ namespace cryptonote if (rpc_config->login) http_login.emplace(std::move(rpc_config->login->username), std::move(rpc_config->login->password).password()); - epee::net_utils::ssl_support_t ssl_support; - const std::string ssl = command_line::get_arg(vm, arg_rpc_ssl); - if (!epee::net_utils::ssl_support_from_string(ssl_support, ssl)) + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect; + if (command_line::get_arg(vm, arg_rpc_ssl_allow_any_cert)) + ssl_options.verification = epee::net_utils::ssl_verification_t::none; + else { - MFATAL("Invalid RPC SSL support: " << ssl); - return false; + std::string ssl_ca_path = command_line::get_arg(vm, arg_rpc_ssl_ca_certificates); + const std::vector<std::string> ssl_allowed_fingerprint_strings = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints); + std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ ssl_allowed_fingerprint_strings.size() }; + std::transform(ssl_allowed_fingerprint_strings.begin(), ssl_allowed_fingerprint_strings.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); + + if (!ssl_ca_path.empty() || !ssl_allowed_fingerprints.empty()) + ssl_options = epee::net_utils::ssl_options_t{std::move(ssl_allowed_fingerprints), std::move(ssl_ca_path)}; } - const std::string ssl_private_key = command_line::get_arg(vm, arg_rpc_ssl_private_key); - const std::string ssl_certificate = command_line::get_arg(vm, arg_rpc_ssl_certificate); - const std::vector<std::string> ssl_allowed_certificate_paths = command_line::get_arg(vm, arg_rpc_ssl_allowed_certificates); - std::list<std::string> ssl_allowed_certificates; - for (const std::string &path: ssl_allowed_certificate_paths) + + ssl_options.auth = epee::net_utils::ssl_authentication_t{ + command_line::get_arg(vm, arg_rpc_ssl_private_key), command_line::get_arg(vm, arg_rpc_ssl_certificate) + }; + + // user specified CA file or fingeprints implies enabled SSL by default + if (ssl_options.verification != epee::net_utils::ssl_verification_t::user_certificates || !command_line::is_arg_defaulted(vm, arg_rpc_ssl)) { - ssl_allowed_certificates.push_back({}); - if (!epee::file_io_utils::load_file_to_string(path, ssl_allowed_certificates.back())) + const std::string ssl = command_line::get_arg(vm, arg_rpc_ssl); + if (!epee::net_utils::ssl_support_from_string(ssl_options.support, ssl)) { - MERROR("Failed to load certificate: " << path); - ssl_allowed_certificates.back() = std::string(); + MFATAL("Invalid RPC SSL support: " << ssl); + return false; } } - const std::vector<std::string> ssl_allowed_fingerprint_strings = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints); - std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ ssl_allowed_fingerprint_strings.size() }; - std::transform(ssl_allowed_fingerprint_strings.begin(), ssl_allowed_fingerprint_strings.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); - const bool ssl_allow_any_cert = command_line::get_arg(vm, arg_rpc_ssl_allow_any_cert); - auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); }; return epee::http_server_impl_base<core_rpc_server, connection_context>::init( - rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), - ssl_support, std::make_pair(ssl_private_key, ssl_certificate), std::move(ssl_allowed_certificates), std::move(ssl_allowed_fingerprints), ssl_allow_any_cert + rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), std::move(ssl_options) ); } //------------------------------------------------------------------------------------------------------------------------------ @@ -193,7 +202,9 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_HEIGHT>(invoke_http_mode::JON, "/getheight", req, res, r)) return r; - res.height = m_core.get_current_blockchain_height(); + crypto::hash hash; + m_core.get_blockchain_top(res.height, hash); + res.hash = string_tools::pod_to_hex(hash); res.status = CORE_RPC_STATUS_OK; return true; } @@ -219,7 +230,7 @@ namespace cryptonote ++res.height; // turn top block height into blockchain height res.top_block_hash = string_tools::pod_to_hex(top_hash); res.target_height = m_core.get_target_blockchain_height(); - res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(); + store_difficulty(m_core.get_blockchain_storage().get_difficulty_for_next_block(), res.difficulty, res.wide_difficulty, res.difficulty_top64); res.target = m_core.get_blockchain_storage().get_difficulty_target(); res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase res.tx_pool_size = m_core.get_pool_transactions_count(); @@ -236,10 +247,10 @@ namespace cryptonote res.testnet = net_type == TESTNET; res.stagenet = net_type == STAGENET; res.nettype = net_type == MAINNET ? "mainnet" : net_type == TESTNET ? "testnet" : net_type == STAGENET ? "stagenet" : "fakechain"; - res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1); + store_difficulty(m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1), + res.cumulative_difficulty, res.wide_cumulative_difficulty, res.cumulative_difficulty_top64); res.block_size_limit = res.block_weight_limit = m_core.get_blockchain_storage().get_current_cumulative_block_weight_limit(); res.block_size_median = res.block_weight_median = m_core.get_blockchain_storage().get_current_cumulative_block_weight_median(); - res.status = CORE_RPC_STATUS_OK; res.start_time = restricted ? 0 : (uint64_t)m_core.get_start_time(); res.free_space = restricted ? std::numeric_limits<uint64_t>::max() : m_core.get_free_space(); res.offline = m_core.offline(); @@ -257,6 +268,25 @@ namespace cryptonote res.database_size = round_up(res.database_size, 5ull* 1024 * 1024 * 1024); res.update_available = restricted ? false : m_core.is_update_available(); res.version = restricted ? "" : MONERO_VERSION; + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_net_stats(const COMMAND_RPC_GET_NET_STATS::request& req, COMMAND_RPC_GET_NET_STATS::response& res, const connection_context *ctx) + { + PERF_TIMER(on_get_net_stats); + // No bootstrap daemon check: Only ever get stats about local server + res.start_time = (uint64_t)m_core.get_start_time(); + { + CRITICAL_REGION_LOCAL(epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_in); + epee::net_utils::network_throttle_manager::get_global_throttle_in().get_stats(res.total_packets_in, res.total_bytes_in); + } + { + CRITICAL_REGION_LOCAL(epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_out); + epee::net_utils::network_throttle_manager::get_global_throttle_out().get_stats(res.total_packets_out, res.total_bytes_out); + } + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -466,6 +496,7 @@ namespace cryptonote cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::request req_bin; req_bin.outputs = req.outputs; + req_bin.get_txid = req.get_txid; cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::response res_bin; if(!m_core.get_outs(req_bin, res_bin)) { @@ -621,30 +652,61 @@ namespace cryptonote e.prunable_hash = epee::string_tools::pod_to_hex(std::get<2>(tx)); if (req.split || req.prune || std::get<3>(tx).empty()) { + // use splitted form with pruned and prunable (filled only when prune=false and the daemon has it), leaving as_hex as empty e.pruned_as_hex = string_tools::buff_to_hex_nodelimer(std::get<1>(tx)); if (!req.prune) e.prunable_as_hex = string_tools::buff_to_hex_nodelimer(std::get<3>(tx)); - } - else - { - cryptonote::blobdata tx_data; - if (req.prune) - tx_data = std::get<1>(tx); - else - tx_data = std::get<1>(tx) + std::get<3>(tx); - e.as_hex = string_tools::buff_to_hex_nodelimer(tx_data); - if (req.decode_as_json && !tx_data.empty()) + if (req.decode_as_json) { + cryptonote::blobdata tx_data; cryptonote::transaction t; - if (cryptonote::parse_and_validate_tx_from_blob(tx_data, t)) + if (req.prune || std::get<3>(tx).empty()) { - if (req.prune) + // decode pruned tx to JSON + tx_data = std::get<1>(tx); + if (cryptonote::parse_and_validate_tx_base_from_blob(tx_data, t)) { pruned_transaction pruned_tx{t}; e.as_json = obj_to_json_str(pruned_tx); } else + { + res.status = "Failed to parse and validate pruned tx from blob"; + return true; + } + } + else + { + // decode full tx to JSON + tx_data = std::get<1>(tx) + std::get<3>(tx); + if (cryptonote::parse_and_validate_tx_from_blob(tx_data, t)) + { e.as_json = obj_to_json_str(t); + } + else + { + res.status = "Failed to parse and validate tx from blob"; + return true; + } + } + } + } + else + { + // use non-splitted form, leaving pruned_as_hex and prunable_as_hex as empty + cryptonote::blobdata tx_data = std::get<1>(tx) + std::get<3>(tx); + e.as_hex = string_tools::buff_to_hex_nodelimer(tx_data); + if (req.decode_as_json) + { + cryptonote::transaction t; + if (cryptonote::parse_and_validate_tx_from_blob(tx_data, t)) + { + e.as_json = obj_to_json_str(t); + } + else + { + res.status = "Failed to parse and validate tx from blob"; + return true; } } } @@ -914,6 +976,7 @@ namespace cryptonote const miner& lMiner = m_core.get_miner(); res.active = lMiner.is_mining(); res.is_background_mining_enabled = lMiner.get_is_background_mining_enabled(); + store_difficulty(m_core.get_blockchain_storage().get_difficulty_for_next_block(), res.difficulty, res.wide_difficulty, res.difficulty_top64); res.block_target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; if ( lMiner.is_mining() ) { @@ -1196,13 +1259,25 @@ namespace cryptonote block b; cryptonote::blobdata blob_reserve; blob_reserve.resize(req.reserve_size, 0); - if(!m_core.get_block_template(b, info.address, res.difficulty, res.height, res.expected_reward, blob_reserve)) + cryptonote::difficulty_type wdiff; + crypto::hash prev_block; + if (!req.prev_block.empty()) + { + if (!epee::string_tools::hex_to_pod(req.prev_block, prev_block)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Invalid prev_block"; + return false; + } + } + if(!m_core.get_block_template(b, req.prev_block.empty() ? NULL : &prev_block, info.address, wdiff, res.height, res.expected_reward, blob_reserve)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: failed to create block template"; LOG_ERROR("Failed to create block template"); return false; } + store_difficulty(wdiff, res.difficulty, res.wide_difficulty, res.difficulty_top64); blobdata block_blob = t_serializable_object_to_blob(b); crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); if(tx_pub_key == crypto::null_pkey) @@ -1281,7 +1356,8 @@ namespace cryptonote return false; } - if(!m_core.handle_block_found(b)) + block_verification_context bvc; + if(!m_core.handle_block_found(b, bvc)) { error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED; error_resp.message = "Block not accepted"; @@ -1313,15 +1389,17 @@ namespace cryptonote template_req.reserve_size = 1; template_req.wallet_address = req.wallet_address; + template_req.prev_block = req.prev_block; submit_req.push_back(boost::value_initialized<std::string>()); res.height = m_core.get_blockchain_storage().get_current_blockchain_height(); - bool r; + bool r = CORE_RPC_STATUS_OK; for(size_t i = 0; i < req.amount_of_blocks; i++) { r = on_getblocktemplate(template_req, template_res, error_resp, ctx); res.status = template_res.status; + template_req.prev_block.clear(); if (!r) return false; @@ -1339,6 +1417,7 @@ namespace cryptonote error_resp.message = "Wrong block blob"; return false; } + b.nonce = req.starting_nonce; miner::find_nonce_for_given_block(b, template_res.difficulty, template_res.height); submit_req.front() = string_tools::buff_to_hex_nodelimer(block_to_blob(b)); @@ -1347,6 +1426,8 @@ namespace cryptonote if (!r) return false; + res.blocks.push_back(epee::string_tools::pod_to_hex(get_block_hash(b))); + template_req.prev_block = res.blocks.back(); res.height = template_res.height; } @@ -1375,13 +1456,16 @@ namespace cryptonote response.height = height; 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); + store_difficulty(m_core.get_blockchain_storage().block_difficulty(height), + response.difficulty, response.wide_difficulty, response.difficulty_top64); + store_difficulty(m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(height), + response.cumulative_difficulty, response.wide_cumulative_difficulty, response.cumulative_difficulty_top64); 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(); response.pow_hash = fill_pow_hash ? string_tools::pod_to_hex(get_block_longhash(blk, height)) : ""; response.long_term_weight = m_core.get_blockchain_storage().get_db().get_block_long_term_weight(height); + response.miner_tx_hash = string_tools::pod_to_hex(cryptonote::get_transaction_hash(blk.miner_tx)); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1688,63 +1772,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_info_json(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_info_json); - bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_INFO>(invoke_http_mode::JON_RPC, "get_info", req, res, r)) - { - res.bootstrap_daemon_address = m_bootstrap_daemon_address; - crypto::hash top_hash; - m_core.get_blockchain_top(res.height_without_bootstrap, top_hash); - ++res.height_without_bootstrap; // turn top block height into blockchain height - res.was_bootstrap_ever_used = true; - return r; - } - - const bool restricted = m_restricted && ctx; - - crypto::hash top_hash; - m_core.get_blockchain_top(res.height, top_hash); - ++res.height; // turn top block height into blockchain height - res.top_block_hash = string_tools::pod_to_hex(top_hash); - res.target_height = m_core.get_target_blockchain_height(); - res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(); - res.target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; - res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase - res.tx_pool_size = m_core.get_pool_transactions_count(); - res.alt_blocks_count = restricted ? 0 : m_core.get_blockchain_storage().get_alternative_blocks_count(); - uint64_t total_conn = restricted ? 0 : m_p2p.get_public_connections_count(); - res.outgoing_connections_count = restricted ? 0 : m_p2p.get_public_outgoing_connections_count(); - res.incoming_connections_count = restricted ? 0 : (total_conn - res.outgoing_connections_count); - res.rpc_connections_count = restricted ? 0 : get_connections_count(); - res.white_peerlist_size = restricted ? 0 : m_p2p.get_public_white_peers_count(); - res.grey_peerlist_size = restricted ? 0 : m_p2p.get_public_gray_peers_count(); - - cryptonote::network_type net_type = nettype(); - res.mainnet = net_type == MAINNET; - res.testnet = net_type == TESTNET; - res.stagenet = net_type == STAGENET; - res.nettype = net_type == MAINNET ? "mainnet" : net_type == TESTNET ? "testnet" : net_type == STAGENET ? "stagenet" : "fakechain"; - - res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1); - res.block_size_limit = res.block_weight_limit = m_core.get_blockchain_storage().get_current_cumulative_block_weight_limit(); - res.block_size_median = res.block_weight_median = m_core.get_blockchain_storage().get_current_cumulative_block_weight_median(); - res.status = CORE_RPC_STATUS_OK; - res.start_time = restricted ? 0 : (uint64_t)m_core.get_start_time(); - res.free_space = restricted ? std::numeric_limits<uint64_t>::max() : m_core.get_free_space(); - res.offline = m_core.offline(); - res.bootstrap_daemon_address = restricted ? "" : m_bootstrap_daemon_address; - res.height_without_bootstrap = restricted ? 0 : res.height; - if (restricted) - res.was_bootstrap_ever_used = false; - else - { - boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex); - res.was_bootstrap_ever_used = m_was_bootstrap_ever_used; - } - res.database_size = restricted ? 0 : m_core.get_blockchain_storage().get_db().get_database_size(); - res.update_available = restricted ? false : m_core.is_update_available(); - res.version = restricted ? "" : MONERO_VERSION; - return true; + return on_get_info(req, res, ctx); } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_hard_fork_info(const COMMAND_RPC_HARD_FORK_INFO::request& req, COMMAND_RPC_HARD_FORK_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) @@ -1947,7 +1975,9 @@ namespace cryptonote std::list<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains(); for (const auto &i: chains) { - res.chains.push_back(COMMAND_RPC_GET_ALTERNATE_CHAINS::chain_info{epee::string_tools::pod_to_hex(get_block_hash(i.first.bl)), i.first.height, i.second.size(), i.first.cumulative_difficulty, {}, std::string()}); + difficulty_type wdiff = i.first.cumulative_difficulty; + res.chains.push_back(COMMAND_RPC_GET_ALTERNATE_CHAINS::chain_info{epee::string_tools::pod_to_hex(get_block_hash(i.first.bl)), i.first.height, i.second.size(), 0, "", 0, {}, std::string()}); + store_difficulty(wdiff, res.chains.back().difficulty, res.chains.back().wide_difficulty, res.chains.back().difficulty_top64); res.chains.back().block_hashes.reserve(i.second.size()); for (const crypto::hash &block_id: i.second) res.chains.back().block_hashes.push_back(epee::string_tools::pod_to_hex(block_id)); @@ -2400,9 +2430,9 @@ namespace cryptonote , "" }; - const command_line::arg_descriptor<std::vector<std::string>> core_rpc_server::arg_rpc_ssl_allowed_certificates = { - "rpc-ssl-allowed-certificates" - , "List of paths to PEM format certificates of allowed peers (all allowed if empty)" + const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_ssl_ca_certificates = { + "rpc-ssl-ca-certificates" + , "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." }; const command_line::arg_descriptor<std::vector<std::string>> core_rpc_server::arg_rpc_ssl_allowed_fingerprints = { @@ -2412,7 +2442,7 @@ namespace cryptonote const command_line::arg_descriptor<bool> core_rpc_server::arg_rpc_ssl_allow_any_cert = { "rpc-ssl-allow-any-cert" - , "Allow any peer certificate, rather than just those on the allowed list" + , "Allow any peer certificate" , false }; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index fe066b31b..a42ca2494 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -60,7 +60,7 @@ namespace cryptonote static const command_line::arg_descriptor<std::string> arg_rpc_ssl; static const command_line::arg_descriptor<std::string> arg_rpc_ssl_private_key; static const command_line::arg_descriptor<std::string> arg_rpc_ssl_certificate; - static const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_certificates; + static const command_line::arg_descriptor<std::string> arg_rpc_ssl_ca_certificates; static const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_fingerprints; static const command_line::arg_descriptor<bool> arg_rpc_ssl_allow_any_cert; static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_address; @@ -115,6 +115,7 @@ namespace cryptonote MAP_URI_AUTO_JON2_IF("/stop_daemon", on_stop_daemon, COMMAND_RPC_STOP_DAEMON, !m_restricted) MAP_URI_AUTO_JON2("/get_info", on_get_info, COMMAND_RPC_GET_INFO) MAP_URI_AUTO_JON2("/getinfo", on_get_info, COMMAND_RPC_GET_INFO) + MAP_URI_AUTO_JON2_IF("/get_net_stats", on_get_net_stats, COMMAND_RPC_GET_NET_STATS, !m_restricted) MAP_URI_AUTO_JON2("/get_limit", on_get_limit, COMMAND_RPC_GET_LIMIT) MAP_URI_AUTO_JON2_IF("/set_limit", on_set_limit, COMMAND_RPC_SET_LIMIT, !m_restricted) MAP_URI_AUTO_JON2_IF("/out_peers", on_out_peers, COMMAND_RPC_OUT_PEERS, !m_restricted) @@ -179,6 +180,7 @@ namespace cryptonote bool on_get_outs_bin(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res, const connection_context *ctx = NULL); bool on_get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res, const connection_context *ctx = NULL); bool on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, const connection_context *ctx = NULL); + bool on_get_net_stats(const COMMAND_RPC_GET_NET_STATS::request& req, COMMAND_RPC_GET_NET_STATS::response& res, const connection_context *ctx = NULL); bool on_save_bc(const COMMAND_RPC_SAVE_BC::request& req, COMMAND_RPC_SAVE_BC::response& res, const connection_context *ctx = NULL); bool on_get_peer_list(const COMMAND_RPC_GET_PEER_LIST::request& req, COMMAND_RPC_GET_PEER_LIST::response& res, const connection_context *ctx = NULL); bool on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res, const connection_context *ctx = NULL); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index f65c7c8dd..d2aba8d67 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -84,7 +84,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 4 +#define CORE_RPC_VERSION_MINOR 5 #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) @@ -102,11 +102,13 @@ namespace cryptonote uint64_t height; std::string status; bool untrusted; + std::string hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(height) KV_SERIALIZE(status) KV_SERIALIZE(untrusted) + KV_SERIALIZE(hash) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -252,223 +254,6 @@ namespace cryptonote }; //----------------------------------------------- - struct COMMAND_RPC_GET_ADDRESS_TXS - { - struct request_t - { - std::string address; - std::string view_key; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) - KV_SERIALIZE(view_key) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct spent_output { - uint64_t amount; - std::string key_image; - std::string tx_pub_key; - uint64_t out_index; - uint32_t mixin; - - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(key_image) - KV_SERIALIZE(tx_pub_key) - KV_SERIALIZE(out_index) - KV_SERIALIZE(mixin) - END_KV_SERIALIZE_MAP() - }; - - struct transaction - { - uint64_t id; - std::string hash; - uint64_t timestamp; - uint64_t total_received; - uint64_t total_sent; - uint64_t unlock_time; - uint64_t height; - std::list<spent_output> spent_outputs; - std::string payment_id; - bool coinbase; - bool mempool; - uint32_t mixin; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(id) - KV_SERIALIZE(hash) - KV_SERIALIZE(timestamp) - KV_SERIALIZE(total_received) - KV_SERIALIZE(total_sent) - KV_SERIALIZE(unlock_time) - KV_SERIALIZE(height) - KV_SERIALIZE(spent_outputs) - KV_SERIALIZE(payment_id) - KV_SERIALIZE(coinbase) - KV_SERIALIZE(mempool) - KV_SERIALIZE(mixin) - END_KV_SERIALIZE_MAP() - }; - - - struct response_t - { - //std::list<std::string> txs_as_json; - uint64_t total_received; - uint64_t total_received_unlocked = 0; // OpenMonero only - uint64_t scanned_height; - std::vector<transaction> transactions; - uint64_t blockchain_height; - uint64_t scanned_block_height; - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(total_received) - KV_SERIALIZE(total_received_unlocked) - KV_SERIALIZE(scanned_height) - KV_SERIALIZE(transactions) - KV_SERIALIZE(blockchain_height) - KV_SERIALIZE(scanned_block_height) - KV_SERIALIZE(status) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - - //----------------------------------------------- - struct COMMAND_RPC_GET_ADDRESS_INFO - { - struct request_t - { - std::string address; - std::string view_key; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) - KV_SERIALIZE(view_key) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct spent_output - { - uint64_t amount; - std::string key_image; - std::string tx_pub_key; - uint64_t out_index; - uint32_t mixin; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(key_image) - KV_SERIALIZE(tx_pub_key) - KV_SERIALIZE(out_index) - KV_SERIALIZE(mixin) - END_KV_SERIALIZE_MAP() - }; - - struct response_t - { - uint64_t locked_funds; - uint64_t total_received; - uint64_t total_sent; - uint64_t scanned_height; - uint64_t scanned_block_height; - uint64_t start_height; - uint64_t transaction_height; - uint64_t blockchain_height; - std::list<spent_output> spent_outputs; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(locked_funds) - KV_SERIALIZE(total_received) - KV_SERIALIZE(total_sent) - KV_SERIALIZE(scanned_height) - KV_SERIALIZE(scanned_block_height) - KV_SERIALIZE(start_height) - KV_SERIALIZE(transaction_height) - KV_SERIALIZE(blockchain_height) - KV_SERIALIZE(spent_outputs) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - - //----------------------------------------------- - struct COMMAND_RPC_GET_UNSPENT_OUTS - { - struct request_t - { - std::string amount; - std::string address; - std::string view_key; - // OpenMonero specific - uint64_t mixin; - bool use_dust; - std::string dust_threshold; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(address) - KV_SERIALIZE(view_key) - KV_SERIALIZE(mixin) - KV_SERIALIZE(use_dust) - KV_SERIALIZE(dust_threshold) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - - struct output { - uint64_t amount; - std::string public_key; - uint64_t index; - uint64_t global_index; - std::string rct; - std::string tx_hash; - std::string tx_pub_key; - std::string tx_prefix_hash; - std::vector<std::string> spend_key_images; - uint64_t timestamp; - uint64_t height; - - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(public_key) - KV_SERIALIZE(index) - KV_SERIALIZE(global_index) - KV_SERIALIZE(rct) - KV_SERIALIZE(tx_hash) - KV_SERIALIZE(tx_pub_key) - KV_SERIALIZE(tx_prefix_hash) - KV_SERIALIZE(spend_key_images) - KV_SERIALIZE(timestamp) - KV_SERIALIZE(height) - END_KV_SERIALIZE_MAP() - }; - - struct response_t - { - uint64_t amount; - std::list<output> outputs; - uint64_t per_kb_fee; - std::string status; - std::string reason; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(outputs) - KV_SERIALIZE(per_kb_fee) - KV_SERIALIZE(status) - KV_SERIALIZE(reason) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - - //----------------------------------------------- struct COMMAND_RPC_GET_RANDOM_OUTS { struct request_t @@ -548,72 +333,6 @@ namespace cryptonote typedef epee::misc_utils::struct_init<response_t> response; }; //----------------------------------------------- - struct COMMAND_RPC_LOGIN - { - struct request_t - { - std::string address; - std::string view_key; - bool create_account; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) - KV_SERIALIZE(view_key) - KV_SERIALIZE(create_account) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct response_t - { - std::string status; - std::string reason; - bool new_address; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) - KV_SERIALIZE(reason) - KV_SERIALIZE(new_address) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - //----------------------------------------------- - struct COMMAND_RPC_IMPORT_WALLET_REQUEST - { - struct request_t - { - std::string address; - std::string view_key; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) - KV_SERIALIZE(view_key) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct response_t - { - std::string payment_id; - uint64_t import_fee; - bool new_request; - bool request_fulfilled; - std::string payment_address; - std::string status; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(payment_id) - KV_SERIALIZE(import_fee) - KV_SERIALIZE(new_request) - KV_SERIALIZE(request_fulfilled) - KV_SERIALIZE(payment_address) - KV_SERIALIZE(status) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - //----------------------------------------------- struct COMMAND_RPC_GET_TRANSACTIONS { struct request_t @@ -811,9 +530,11 @@ namespace cryptonote struct request_t { std::vector<get_outputs_out> outputs; + bool get_txid; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(outputs) + KV_SERIALIZE(get_txid) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -943,6 +664,8 @@ namespace cryptonote uint64_t height; uint64_t target_height; uint64_t difficulty; + std::string wide_difficulty; + uint64_t difficulty_top64; uint64_t target; uint64_t tx_count; uint64_t tx_pool_size; @@ -958,6 +681,8 @@ namespace cryptonote std::string nettype; std::string top_block_hash; uint64_t cumulative_difficulty; + std::string wide_cumulative_difficulty; + uint64_t cumulative_difficulty_top64; uint64_t block_size_limit; uint64_t block_weight_limit; uint64_t block_size_median; @@ -978,6 +703,8 @@ namespace cryptonote KV_SERIALIZE(height) KV_SERIALIZE(target_height) KV_SERIALIZE(difficulty) + KV_SERIALIZE(wide_difficulty) + KV_SERIALIZE(difficulty_top64) KV_SERIALIZE(target) KV_SERIALIZE(tx_count) KV_SERIALIZE(tx_pool_size) @@ -993,6 +720,8 @@ namespace cryptonote KV_SERIALIZE(nettype) KV_SERIALIZE(top_block_hash) KV_SERIALIZE(cumulative_difficulty) + KV_SERIALIZE(wide_cumulative_difficulty) + KV_SERIALIZE(cumulative_difficulty_top64) KV_SERIALIZE(block_size_limit) KV_SERIALIZE_OPT(block_weight_limit, (uint64_t)0) KV_SERIALIZE(block_size_median) @@ -1014,6 +743,39 @@ namespace cryptonote //----------------------------------------------- + struct COMMAND_RPC_GET_NET_STATS + { + struct request_t + { + + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + + struct response_t + { + std::string status; + uint64_t start_time; + uint64_t total_packets_in; + uint64_t total_bytes_in; + uint64_t total_packets_out; + uint64_t total_bytes_out; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(start_time) + KV_SERIALIZE(total_packets_in) + KV_SERIALIZE(total_bytes_in) + KV_SERIALIZE(total_packets_out) + KV_SERIALIZE(total_bytes_out) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + //----------------------------------------------- struct COMMAND_RPC_STOP_MINING { struct request_t @@ -1063,6 +825,9 @@ namespace cryptonote uint8_t bg_target; uint32_t block_target; uint64_t block_reward; + uint64_t difficulty; + std::string wide_difficulty; + uint64_t difficulty_top64; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -1078,6 +843,9 @@ namespace cryptonote KV_SERIALIZE(bg_target) KV_SERIALIZE(block_target) KV_SERIALIZE(block_reward) + KV_SERIALIZE(difficulty) + KV_SERIALIZE(wide_difficulty) + KV_SERIALIZE(difficulty_top64) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1138,10 +906,12 @@ namespace cryptonote { uint64_t reserve_size; //max 255 bytes std::string wallet_address; + std::string prev_block; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(reserve_size) KV_SERIALIZE(wallet_address) + KV_SERIALIZE(prev_block) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1149,6 +919,8 @@ namespace cryptonote struct response_t { uint64_t difficulty; + std::string wide_difficulty; + uint64_t difficulty_top64; uint64_t height; uint64_t reserved_offset; uint64_t expected_reward; @@ -1160,6 +932,8 @@ namespace cryptonote BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(difficulty) + KV_SERIALIZE(wide_difficulty) + KV_SERIALIZE(difficulty_top64) KV_SERIALIZE(height) KV_SERIALIZE(reserved_offset) KV_SERIALIZE(expected_reward) @@ -1194,10 +968,14 @@ namespace cryptonote { uint64_t amount_of_blocks; std::string wallet_address; + std::string prev_block; + uint32_t starting_nonce; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amount_of_blocks) KV_SERIALIZE(wallet_address) + KV_SERIALIZE(prev_block) + KV_SERIALIZE_OPT(starting_nonce, (uint32_t)0) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1205,10 +983,12 @@ namespace cryptonote struct response_t { uint64_t height; + std::vector<std::string> blocks; std::string status; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(height) + KV_SERIALIZE(blocks) KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; @@ -1226,14 +1006,19 @@ namespace cryptonote uint64_t height; uint64_t depth; std::string hash; - difficulty_type difficulty; - difficulty_type cumulative_difficulty; + uint64_t difficulty; + std::string wide_difficulty; + uint64_t difficulty_top64; + uint64_t cumulative_difficulty; + std::string wide_cumulative_difficulty; + uint64_t cumulative_difficulty_top64; uint64_t reward; uint64_t block_size; uint64_t block_weight; uint64_t num_txes; std::string pow_hash; uint64_t long_term_weight; + std::string miner_tx_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(major_version) @@ -1246,13 +1031,18 @@ namespace cryptonote KV_SERIALIZE(depth) KV_SERIALIZE(hash) KV_SERIALIZE(difficulty) + KV_SERIALIZE(wide_difficulty) + KV_SERIALIZE(difficulty_top64) KV_SERIALIZE(cumulative_difficulty) + KV_SERIALIZE(wide_cumulative_difficulty) + KV_SERIALIZE(cumulative_difficulty_top64) KV_SERIALIZE(reward) KV_SERIALIZE(block_size) KV_SERIALIZE_OPT(block_weight, (uint64_t)0) KV_SERIALIZE(num_txes) KV_SERIALIZE(pow_hash) KV_SERIALIZE_OPT(long_term_weight, (uint64_t)0) + KV_SERIALIZE(miner_tx_hash) END_KV_SERIALIZE_MAP() }; @@ -2248,6 +2038,8 @@ namespace cryptonote uint64_t height; uint64_t length; uint64_t difficulty; + std::string wide_difficulty; + uint64_t difficulty_top64; std::vector<std::string> block_hashes; std::string main_chain_parent_block; @@ -2256,6 +2048,8 @@ namespace cryptonote KV_SERIALIZE(height) KV_SERIALIZE(length) KV_SERIALIZE(difficulty) + KV_SERIALIZE(wide_difficulty) + KV_SERIALIZE(difficulty_top64) KV_SERIALIZE(block_hashes) KV_SERIALIZE(main_chain_parent_block) END_KV_SERIALIZE_MAP() diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index bde2339bc..540afe6b9 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -436,7 +436,8 @@ namespace rpc auto& chain = m_core.get_blockchain_storage(); - res.info.difficulty = chain.get_difficulty_for_next_block(); + res.info.wide_difficulty = chain.get_difficulty_for_next_block(); + res.info.difficulty = (res.info.wide_difficulty << 64 >> 64).convert_to<uint64_t>(); res.info.target = chain.get_difficulty_target(); @@ -457,7 +458,8 @@ namespace rpc res.info.mainnet = m_core.get_nettype() == MAINNET; res.info.testnet = m_core.get_nettype() == TESTNET; res.info.stagenet = m_core.get_nettype() == STAGENET; - res.info.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.info.height - 1); + res.info.wide_cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.info.height - 1); + res.info.cumulative_difficulty = (res.info.wide_cumulative_difficulty << 64 >> 64).convert_to<uint64_t>(); res.info.block_size_limit = res.info.block_weight_limit = m_core.get_blockchain_storage().get_current_cumulative_block_weight_limit(); res.info.block_size_median = res.info.block_weight_median = m_core.get_blockchain_storage().get_current_cumulative_block_weight_median(); res.info.start_time = (uint64_t)m_core.get_start_time(); @@ -826,7 +828,8 @@ namespace rpc header.reward += out.amount; } - header.difficulty = m_core.get_blockchain_storage().block_difficulty(header.height); + header.wide_difficulty = m_core.get_blockchain_storage().block_difficulty(header.height); + header.difficulty = (header.wide_difficulty << 64 >> 64).convert_to<uint64_t>(); return true; } diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h index 26c5038f6..2a43811cf 100644 --- a/src/rpc/message_data_structs.h +++ b/src/rpc/message_data_structs.h @@ -30,6 +30,7 @@ #include "crypto/hash.h" #include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/difficulty.h" #include "ringct/rctSigs.h" #include "rpc/rpc_handler.h" @@ -165,6 +166,7 @@ namespace rpc uint64_t height; uint64_t depth; crypto::hash hash; + cryptonote::difficulty_type wide_difficulty; uint64_t difficulty; uint64_t reward; }; @@ -173,6 +175,7 @@ namespace rpc { uint64_t height; uint64_t target_height; + cryptonote::difficulty_type wide_difficulty; uint64_t difficulty; uint64_t target; uint64_t tx_count; @@ -187,6 +190,7 @@ namespace rpc bool stagenet; std::string nettype; crypto::hash top_block_hash; + cryptonote::difficulty_type wide_cumulative_difficulty; uint64_t cumulative_difficulty; uint64_t block_size_limit; uint64_t block_weight_limit; diff --git a/src/serialization/difficulty_type.h b/src/serialization/difficulty_type.h new file mode 100644 index 000000000..e32e24b78 --- /dev/null +++ b/src/serialization/difficulty_type.h @@ -0,0 +1,65 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "cryptonote_basic/difficulty.h" +#include "serialization.h" + +template<> struct is_basic_type<cryptonote::difficulty_type> { typedef boost::true_type type; }; + +template <template <bool> class Archive> +inline bool do_serialize(Archive<false>& ar, cryptonote::difficulty_type &diff) +{ + uint64_t hi, lo; + ar.serialize_varint(hi); + if (!ar.stream().good()) + return false; + ar.serialize_varint(lo); + if (!ar.stream().good()) + return false; + diff = hi; + diff <<= 64; + diff += lo; + return true; +} + +template <template <bool> class Archive> +inline bool do_serialize(Archive<true>& ar, cryptonote::difficulty_type &diff) +{ + if (!ar.stream().good()) + return false; + const uint64_t hi = (diff >> 64).convert_to<uint64_t>(); + const uint64_t lo = (diff << 64 >> 64).convert_to<uint64_t>(); + ar.serialize_varint(hi); + ar.serialize_varint(lo); + if (!ar.stream().good()) + return false; + return true; +} + diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index d5835609a..2e134931f 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -116,7 +116,9 @@ typedef cryptonote::simple_wallet sw; #define LONG_PAYMENT_ID_SUPPORT_CHECK() \ do { \ if (!m_long_payment_id_support) { \ - fail_msg_writer() << tr("Long payment IDs are obsolete. Use --long-payment-id-support if you really must use one, and warn the recipient they are using an obsolete feature that will disappear in the future."); \ + fail_msg_writer() << tr("Warning: Long payment IDs are obsolete."); \ + fail_msg_writer() << tr("Long payment IDs are not encrypted on the blockchain, and will harm your privacy."); \ + fail_msg_writer() << tr("Use --long-payment-id-support if you really must use one, and warn the recipient they are using an obsolete feature that will disappear in the future."); \ return true; \ } \ } while(0) @@ -149,7 +151,7 @@ namespace const command_line::arg_descriptor<bool> arg_create_address_file = {"create-address-file", sw::tr("Create an address file for new wallets"), false}; const command_line::arg_descriptor<std::string> arg_subaddress_lookahead = {"subaddress-lookahead", tools::wallet2::tr("Set subaddress lookahead sizes to <major>:<minor>"), ""}; const command_line::arg_descriptor<bool> arg_use_english_language_names = {"use-english-language-names", sw::tr("Display English language names"), false}; - const command_line::arg_descriptor<bool> arg_long_payment_id_support = {"long-payment-id-support", sw::tr("Support obsolete long (unencrypted) payment ids"), false}; + const command_line::arg_descriptor<bool> arg_long_payment_id_support = {"long-payment-id-support-bad-for-privacy", sw::tr("Support obsolete long (unencrypted) payment ids (using them harms your privacy)"), false}; const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""}; @@ -233,10 +235,16 @@ namespace const char* USAGE_MMS_AUTO_CONFIG("mms auto_config <auto_config_token>"); const char* USAGE_PRINT_RING("print_ring <key_image> | <txid>"); const char* USAGE_SET_RING("set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"); + const char* USAGE_UNSET_RING("unset_ring <txid> | ( <key_image> [<key_image>...] )"); const char* USAGE_SAVE_KNOWN_RINGS("save_known_rings"); const char* USAGE_MARK_OUTPUT_SPENT("mark_output_spent <amount>/<offset> | <filename> [add]"); const char* USAGE_MARK_OUTPUT_UNSPENT("mark_output_unspent <amount>/<offset>"); const char* USAGE_IS_OUTPUT_SPENT("is_output_spent <amount>/<offset>"); + const char* USAGE_FREEZE("freeze <key_image>"); + const char* USAGE_THAW("thaw <key_image>"); + const char* USAGE_FROZEN("frozen <key_image>"); + const char* USAGE_NET_STATS("net_stats"); + const char* USAGE_WELCOME("welcome"); const char* USAGE_VERSION("version"); const char* USAGE_HELP("help [<command>]"); @@ -1865,6 +1873,38 @@ bool simple_wallet::set_ring(const std::vector<std::string> &args) return true; } +bool simple_wallet::unset_ring(const std::vector<std::string> &args) +{ + crypto::hash txid; + std::vector<crypto::key_image> key_images; + + if (args.size() < 1) + { + PRINT_USAGE(USAGE_UNSET_RING); + return true; + } + + key_images.resize(args.size()); + for (size_t i = 0; i < args.size(); ++i) + { + if (!epee::string_tools::hex_to_pod(args[i], key_images[i])) + { + fail_msg_writer() << tr("Invalid key image or txid"); + return true; + } + } + static_assert(sizeof(crypto::hash) == sizeof(crypto::key_image), "hash and key_image must have the same size"); + memcpy(&txid, &key_images[0], sizeof(txid)); + + if (!m_wallet->unset_ring(key_images) && !m_wallet->unset_ring(txid)) + { + fail_msg_writer() << tr("failed to unset ring"); + return true; + } + + return true; +} + bool simple_wallet::blackball(const std::vector<std::string> &args) { uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets; @@ -2025,6 +2065,99 @@ bool simple_wallet::save_known_rings(const std::vector<std::string> &args) return true; } +bool simple_wallet::freeze_thaw(const std::vector<std::string> &args, bool freeze) +{ + if (args.empty()) + { + fail_msg_writer() << boost::format(tr("usage: %s <key_image>|<pubkey>")) % (freeze ? "freeze" : "thaw"); + return true; + } + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(args[0], ki)) + { + fail_msg_writer() << tr("failed to parse key image"); + return true; + } + try + { + if (freeze) + m_wallet->freeze(ki); + else + m_wallet->thaw(ki); + } + catch (const std::exception &e) + { + fail_msg_writer() << e.what(); + return true; + } + + return true; +} + +bool simple_wallet::freeze(const std::vector<std::string> &args) +{ + return freeze_thaw(args, true); +} + +bool simple_wallet::thaw(const std::vector<std::string> &args) +{ + return freeze_thaw(args, false); +} + +bool simple_wallet::frozen(const std::vector<std::string> &args) +{ + if (args.empty()) + { + size_t ntd = m_wallet->get_num_transfer_details(); + for (size_t i = 0; i < ntd; ++i) + { + if (!m_wallet->frozen(i)) + continue; + const tools::wallet2::transfer_details &td = m_wallet->get_transfer_details(i); + message_writer() << tr("Frozen: ") << td.m_key_image << " " << cryptonote::print_money(td.amount()); + } + } + else + { + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(args[0], ki)) + { + fail_msg_writer() << tr("failed to parse key image"); + return true; + } + if (m_wallet->frozen(ki)) + message_writer() << tr("Frozen: ") << ki; + else + message_writer() << tr("Not frozen: ") << ki; + } + return true; +} + +bool simple_wallet::net_stats(const std::vector<std::string> &args) +{ + message_writer() << std::to_string(m_wallet->get_bytes_sent()) + tr(" bytes sent"); + message_writer() << std::to_string(m_wallet->get_bytes_received()) + tr(" bytes received"); + return true; +} + +bool simple_wallet::welcome(const std::vector<std::string> &args) +{ + message_writer() << tr("Welcome to Monero, the private cryptocurrency."); + message_writer() << ""; + message_writer() << tr("Monero, like Bitcoin, is a cryptocurrency. That is, it is digital money."); + message_writer() << tr("Unlike Bitcoin, your Monero transactions and balance stay private, and not visible to the world by default."); + message_writer() << tr("However, you have the option of making those available to select parties, if you choose to."); + message_writer() << ""; + message_writer() << tr("Monero protects your privacy on the blockchain, and while Monero strives to improve all the time,"); + message_writer() << tr("no privacy technology can be 100% perfect, Monero included."); + message_writer() << tr("Monero cannot protect you from malware, and it may not be as effective as we hope against powerful adversaries."); + message_writer() << tr("Flaws in Monero may be discovered in the future, and attacks may be developed to peek under some"); + message_writer() << tr("of the layers of privacy Monero provides. Be safe and practice defense in depth."); + message_writer() << ""; + message_writer() << tr("Welcome to Monero and financial privacy. For more information, see https://getmonero.org/"); + return true; +} + bool simple_wallet::version(const std::vector<std::string> &args) { message_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; @@ -2519,6 +2652,31 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std: return true; } +bool simple_wallet::set_setup_background_mining(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + tools::wallet2::BackgroundMiningSetupType setup = tools::wallet2::BackgroundMiningMaybe; + if (args[1] == "yes" || args[1] == "1") + setup = tools::wallet2::BackgroundMiningYes; + else if (args[1] == "no" || args[1] == "0") + setup = tools::wallet2::BackgroundMiningNo; + else + { + fail_msg_writer() << tr("invalid argument: must be either 1/yes or 0/no"); + return true; + } + m_wallet->setup_background_mining(setup); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + if (setup == tools::wallet2::BackgroundMiningYes) + start_background_mining(); + else + stop_background_mining(); + } + return true; +} + bool simple_wallet::set_device_name(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -2600,7 +2758,7 @@ simple_wallet::simple_wallet() tr(USAGE_INCOMING_TRANSFERS), tr("Show the incoming transfers, all or filtered by availability and address index.\n\n" "Output format:\n" - "Amount, Spent(\"T\"|\"F\"), \"locked\"|\"unlocked\", RingCT, Global Index, Transaction Hash, Address Index, [Public Key, Key Image] ")); + "Amount, Spent(\"T\"|\"F\"), \"frozen\"|\"locked\"|\"unlocked\", RingCT, Global Index, Transaction Hash, Address Index, [Public Key, Key Image] ")); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), tr(USAGE_PAYMENTS), @@ -2996,6 +3154,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::set_ring, this, _1), tr(USAGE_SET_RING), tr("Set the ring used for a given key image, so it can be reused in a fork")); + m_cmd_binder.set_handler("unset_ring", + boost::bind(&simple_wallet::unset_ring, this, _1), + tr(USAGE_UNSET_RING), + tr("Unsets the ring used for a given key image or transaction")); m_cmd_binder.set_handler("save_known_rings", boost::bind(&simple_wallet::save_known_rings, this, _1), tr(USAGE_SAVE_KNOWN_RINGS), @@ -3012,6 +3174,26 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::blackballed, this, _1), tr(USAGE_IS_OUTPUT_SPENT), tr("Checks whether an output is marked as spent")); + m_cmd_binder.set_handler("freeze", + boost::bind(&simple_wallet::freeze, this, _1), + tr(USAGE_FREEZE), + tr("Freeze a single output by key image so it will not be used")); + m_cmd_binder.set_handler("thaw", + boost::bind(&simple_wallet::thaw, this, _1), + tr(USAGE_THAW), + tr("Thaw a single output by key image so it may be used again")); + m_cmd_binder.set_handler("frozen", + boost::bind(&simple_wallet::frozen, this, _1), + tr(USAGE_FROZEN), + tr("Checks whether a given output is currently frozen by key image")); + m_cmd_binder.set_handler("net_stats", + boost::bind(&simple_wallet::net_stats, this, _1), + tr(USAGE_NET_STATS), + tr("Prints simple network stats")); + m_cmd_binder.set_handler("welcome", + boost::bind(&simple_wallet::welcome, this, _1), + tr(USAGE_WELCOME), + tr("Prints basic info about Monero for first time users")); m_cmd_binder.set_handler("version", boost::bind(&simple_wallet::version, this, _1), tr(USAGE_VERSION), @@ -3040,6 +3222,13 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) case tools::wallet2::AskPasswordOnAction: ask_password_string = "action"; break; case tools::wallet2::AskPasswordToDecrypt: ask_password_string = "decrypt"; break; } + std::string setup_background_mining_string = "invalid"; + switch (m_wallet->setup_background_mining()) + { + case tools::wallet2::BackgroundMiningMaybe: setup_background_mining_string = "maybe"; break; + case tools::wallet2::BackgroundMiningYes: setup_background_mining_string = "yes"; break; + case tools::wallet2::BackgroundMiningNo: setup_background_mining_string = "no"; break; + } success_msg_writer() << "seed = " << seed_language; success_msg_writer() << "always-confirm-transfers = " << m_wallet->always_confirm_transfers(); success_msg_writer() << "print-ring-members = " << m_wallet->print_ring_members(); @@ -3066,6 +3255,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "segregation-height = " << m_wallet->segregation_height(); success_msg_writer() << "ignore-fractional-outputs = " << m_wallet->ignore_fractional_outputs(); success_msg_writer() << "track-uses = " << m_wallet->track_uses(); + success_msg_writer() << "setup-background-mining = " << setup_background_mining_string + tr(" (set this to support the network and to get a chance to receive new monero)"); success_msg_writer() << "device_name = " << m_wallet->device_name(); return true; } @@ -3123,6 +3313,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("segregation-height", set_segregation_height, tr("unsigned integer")); CHECK_SIMPLE_VARIABLE("ignore-fractional-outputs", set_ignore_fractional_outputs, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); @@ -3318,10 +3509,13 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) const network_type nettype = testnet ? TESTNET : stagenet ? STAGENET : MAINNET; epee::wipeable_string multisig_keys; + epee::wipeable_string password; if (!handle_command_line(vm)) return false; + bool welcome = false; + if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_device.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_spend_key.empty()) + (!m_generate_from_keys.empty()) + (!m_generate_from_multisig_keys.empty()) + (!m_generate_from_json.empty()) > 1) { fail_msg_writer() << tr("can't specify more than one of --generate-new-wallet=\"wallet_name\", --wallet-file=\"wallet_name\", --generate-from-view-key=\"wallet_name\", --generate-from-spend-key=\"wallet_name\", --generate-from-keys=\"wallet_name\", --generate-from-multisig-keys=\"wallet_name\", --generate-from-json=\"jsonfilename\" and --generate-from-device=\"wallet_name\""); @@ -3425,7 +3619,6 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); } } - epee::wipeable_string password; if (!m_generate_from_view_key.empty()) { m_wallet_file = m_generate_from_view_key; @@ -3480,6 +3673,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto r = new_wallet(vm, info.address, boost::none, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; } else if (!m_generate_from_spend_key.empty()) { @@ -3500,6 +3694,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto r = new_wallet(vm, m_recovery_key, true, false, ""); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; } else if (!m_generate_from_keys.empty()) { @@ -3577,6 +3772,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto r = new_wallet(vm, info.address, spendkey, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; } // Asks user for all the data required to merge secret keys from multisig wallets into one master wallet, which then gets full control of the multisig wallet. The resulting wallet will be the same as any other regular wallet. @@ -3710,6 +3906,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto r = new_wallet(vm, info.address, spendkey, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; } else if (!m_generate_from_json.empty()) @@ -3736,6 +3933,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto r = new_wallet(vm); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; // if no block_height is specified, assume its a new account and start it "now" if(m_wallet->get_refresh_from_block_height() == 0) { { @@ -3767,6 +3965,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; } if (m_restoring && m_generate_from_json.empty() && m_generate_from_device.empty()) @@ -3870,8 +4069,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("can't specify --subaddress-lookahead and --wallet-file at the same time"); return false; } - bool r = open_wallet(vm); + auto r = open_wallet(vm); CHECK_AND_ASSERT_MES(r, false, tr("failed to open account")); + password = *r; } if (!m_wallet) { @@ -3887,6 +4087,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) m_wallet->callback(this); + check_background_mining(password); + + if (welcome) + message_writer(console_color_yellow, true) << tr("If you are new to Monero, type \"welcome\" for a brief overview."); + return true; } //---------------------------------------------------------------------------------------------------- @@ -4256,12 +4461,12 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr return std::move(password); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) +boost::optional<epee::wipeable_string> simple_wallet::open_wallet(const boost::program_options::variables_map& vm) { if (!tools::wallet2::wallet_valid_path_format(m_wallet_file)) { fail_msg_writer() << tr("wallet file path not valid: ") << m_wallet_file; - return false; + return {}; } bool keys_file_exists; @@ -4271,7 +4476,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) if(!keys_file_exists) { fail_msg_writer() << tr("Key file not found. Failed to open wallet"); - return false; + return {}; } epee::wipeable_string password; @@ -4282,7 +4487,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) password = std::move(std::move(rc.second).password()); if (!m_wallet) { - return false; + return {}; } m_wallet->callback(this); @@ -4308,7 +4513,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) { bool is_deterministic; { - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return {};); is_deterministic = m_wallet->is_deterministic(); } if (is_deterministic) @@ -4317,7 +4522,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) "a deprecated version of the wallet. Please proceed to upgrade your wallet.\n"); std::string mnemonic_language = get_mnemonic_language(); if (mnemonic_language.empty()) - return false; + return {}; m_wallet->set_seed_language(mnemonic_language); m_wallet->rewrite(m_wallet_file, password); @@ -4349,14 +4554,14 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) if (password_is_correct) fail_msg_writer() << boost::format(tr("You may want to remove the file \"%s\" and try again")) % m_wallet_file; } - return false; + return {}; } success_msg_writer() << "**********************************************************************\n" << tr("Use the \"help\" command to see the list of available commands.\n") << tr("Use \"help <command>\" to see a command's documentation.\n") << "**********************************************************************"; - return true; + return std::move(password); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::close_wallet() @@ -4437,7 +4642,118 @@ bool simple_wallet::save_watch_only(const std::vector<std::string> &args/* = std } return true; } +//---------------------------------------------------------------------------------------------------- +void simple_wallet::start_background_mining() +{ + COMMAND_RPC_MINING_STATUS::request reqq; + COMMAND_RPC_MINING_STATUS::response resq; + bool r = m_wallet->invoke_http_json("/mining_status", reqq, resq); + std::string err = interpret_rpc_response(r, resq.status); + if (!r) + return; + if (!err.empty()) + { + fail_msg_writer() << tr("Failed to query mining status: ") << err; + return; + } + if (!resq.is_background_mining_enabled) + { + COMMAND_RPC_START_MINING::request req; + COMMAND_RPC_START_MINING::response res; + req.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); + req.threads_count = 1; + req.do_background_mining = true; + req.ignore_battery = false; + bool r = m_wallet->invoke_http_json("/start_mining", req, res); + std::string err = interpret_rpc_response(r, res.status); + if (!err.empty()) + { + fail_msg_writer() << tr("Failed to setup background mining: ") << err; + return; + } + } + success_msg_writer() << tr("Background mining enabled. Thank you for supporting the Monero network."); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::stop_background_mining() +{ + COMMAND_RPC_MINING_STATUS::request reqq; + COMMAND_RPC_MINING_STATUS::response resq; + bool r = m_wallet->invoke_http_json("/mining_status", reqq, resq); + if (!r) + return; + std::string err = interpret_rpc_response(r, resq.status); + if (!err.empty()) + { + fail_msg_writer() << tr("Failed to query mining status: ") << err; + return; + } + if (resq.is_background_mining_enabled) + { + COMMAND_RPC_STOP_MINING::request req; + COMMAND_RPC_STOP_MINING::response res; + bool r = m_wallet->invoke_http_json("/stop_mining", req, res); + std::string err = interpret_rpc_response(r, res.status); + if (!err.empty()) + { + fail_msg_writer() << tr("Failed to setup background mining: ") << err; + return; + } + } + message_writer(console_color_red, false) << tr("Background mining not enabled. Run \"set setup-background-mining 1\" to change."); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::check_background_mining(const epee::wipeable_string &password) +{ + tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining(); + if (setup == tools::wallet2::BackgroundMiningNo) + { + message_writer(console_color_red, false) << tr("Background mining not enabled. Run \"set setup-background-mining 1\" to change."); + return; + } + + if (!m_wallet->is_trusted_daemon()) + { + message_writer() << tr("Using an untrusted daemon, skipping background mining check"); + return; + } + COMMAND_RPC_MINING_STATUS::request req; + COMMAND_RPC_MINING_STATUS::response res; + bool r = m_wallet->invoke_http_json("/mining_status", req, res); + std::string err = interpret_rpc_response(r, res.status); + bool is_background_mining_enabled = false; + if (err.empty()) + is_background_mining_enabled = res.is_background_mining_enabled; + + if (is_background_mining_enabled) + { + // already active, nice + m_wallet->setup_background_mining(tools::wallet2::BackgroundMiningYes); + m_wallet->rewrite(m_wallet_file, password); + start_background_mining(); + return; + } + if (res.active) + return; + + if (setup == tools::wallet2::BackgroundMiningMaybe) + { + message_writer() << tr("The daemon is not set up to background mine."); + message_writer() << tr("With background mining enabled, the daemon will mine when idle and not on batttery."); + message_writer() << tr("Enabling this supports the network you are using, and makes you eligible for receiving new monero"); + std::string accepted = input_line(tr("Do you want to do it now? (Y/Yes/N/No): ")); + if (std::cin.eof() || !command_line::is_yes(accepted)) { + m_wallet->setup_background_mining(tools::wallet2::BackgroundMiningNo); + m_wallet->rewrite(m_wallet_file, password); + message_writer(console_color_red, false) << tr("Background mining not enabled. Set setup-background-mining to 1 to change."); + return; + } + m_wallet->setup_background_mining(tools::wallet2::BackgroundMiningYes); + m_wallet->rewrite(m_wallet_file, password); + start_background_mining(); + } +} //---------------------------------------------------------------------------------------------------- bool simple_wallet::start_mining(const std::vector<std::string>& args) { @@ -4749,8 +5065,7 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo LOCK_IDLE_SCOPE(); crypto::hash transfer_hash_pre{}; - uint64_t height_pre, height_post; - + uint64_t height_pre = 0, height_post; if (reset != ResetNone) { if (reset == ResetSoftKeepKI) @@ -4863,10 +5178,15 @@ bool simple_wallet::show_balance_unlocked(bool detailed) success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0}); const std::string tag = m_wallet->get_account_tags().second[m_current_subaddress_account]; success_msg_writer() << tr("Tag: ") << (tag.empty() ? std::string{tr("(No tag assigned)")} : tag); + uint64_t blocks_to_unlock; + uint64_t unlocked_balance = m_wallet->unlocked_balance(m_current_subaddress_account, &blocks_to_unlock); + std::string unlock_time_message; + if (blocks_to_unlock > 0) + unlock_time_message = (boost::format(" (%lu block(s) to unlock)") % blocks_to_unlock).str(); success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance(m_current_subaddress_account)) << ", " - << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)) << extra; + << tr("unlocked balance: ") << print_money(unlocked_balance) << unlock_time_message << extra; std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account); - std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account); + std::map<uint32_t, std::pair<uint64_t, uint64_t>> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account); if (!detailed || balance_per_subaddress.empty()) return true; success_msg_writer() << tr("Balance per address:"); @@ -4878,7 +5198,7 @@ bool simple_wallet::show_balance_unlocked(bool detailed) cryptonote::subaddress_index subaddr_index = {m_current_subaddress_account, i.first}; std::string address_str = m_wallet->get_subaddress_as_str(subaddr_index).substr(0, 6); uint64_t num_unspent_outputs = std::count_if(transfers.begin(), transfers.end(), [&subaddr_index](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == subaddr_index; }); - success_msg_writer() << boost::format(tr("%8u %6s %21s %21s %7u %21s")) % i.first % address_str % print_money(i.second) % print_money(unlocked_balance_per_subaddress[i.first]) % num_unspent_outputs % m_wallet->get_subaddress_label(subaddr_index); + success_msg_writer() << boost::format(tr("%8u %6s %21s %21s %7u %21s")) % i.first % address_str % print_money(i.second) % print_money(unlocked_balance_per_subaddress[i.first].first) % num_unspent_outputs % m_wallet->get_subaddress_label(subaddr_index); } return true; } @@ -4987,7 +5307,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args boost::format("%21s%8s%12s%8s%16u%68s%16u%s") % print_money(td.amount()) % (td.m_spent ? tr("T") : tr("F")) % - (m_wallet->is_transfer_unlocked(td) ? tr("unlocked") : tr("locked")) % + (m_wallet->frozen(td) ? tr("[frozen]") : m_wallet->is_transfer_unlocked(td) ? tr("unlocked") : tr("locked")) % (td.is_rct() ? tr("RingCT") : tr("-")) % td.m_global_output_index % td.m_txid % @@ -5212,7 +5532,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending } const cryptonote::tx_source_entry& source = *sptr; - ostr << boost::format(tr("\nInput %llu/%llu: amount=%s")) % (i + 1) % tx.vin.size() % print_money(source.amount); + ostr << boost::format(tr("\nInput %llu/%llu (%s): amount=%s")) % (i + 1) % tx.vin.size() % epee::string_tools::pod_to_hex(in_key.k_image) % print_money(source.amount); // convert relative offsets of ring member keys into absolute offsets (indices) associated with the amount std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key.key_offsets); // get block heights from which those ring member keys originated @@ -5355,7 +5675,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri r = add_extra_nonce_to_tx_extra(extra, extra_nonce); local_args.pop_back(); payment_id_seen = true; - message_writer() << tr("Unencrypted payment IDs are bad for privacy: ask the recipient to use subaddresses instead"); + message_writer() << tr("Warning: Unencrypted payment IDs will harm your privacy: ask the recipient to use subaddresses instead"); } if(!r) { @@ -5465,7 +5785,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri { LONG_PAYMENT_ID_SUPPORT_CHECK(); set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - message_writer() << tr("Unencrypted payment IDs are bad for privacy: ask the recipient to use subaddresses instead"); + message_writer() << tr("Warning: Unencrypted payment IDs will harm your privacy: ask the recipient to use subaddresses instead"); } else { @@ -6569,7 +6889,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, { const tx_destination_entry &entry = cd.splitted_dsts[d]; std::string address, standard_address = get_account_address_as_str(m_wallet->nettype(), entry.is_subaddress, entry.addr); - if (has_encrypted_payment_id && !entry.is_subaddress) + if (has_encrypted_payment_id && !entry.is_subaddress && standard_address != entry.original) { address = get_account_integrated_address_as_str(m_wallet->nettype(), entry.addr, payment_id8); address += std::string(" (" + standard_address + " with encrypted payment id " + epee::string_tools::pod_to_hex(payment_id8) + ")"); @@ -6871,11 +7191,6 @@ bool simple_wallet::set_tx_key(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) { - if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR) - { - fail_msg_writer() << tr("command not supported by HW wallet"); - return true; - } if (args.size() != 2 && args.size() != 3) { PRINT_USAGE(USAGE_GET_TX_PROOF); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 7bcb92190..76d446ba5 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -102,7 +102,7 @@ namespace cryptonote boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const epee::wipeable_string &multisig_keys, const std::string &old_language); boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm); - bool open_wallet(const boost::program_options::variables_map& vm); + boost::optional<epee::wipeable_string> open_wallet(const boost::program_options::variables_map& vm); bool close_wallet(); bool viewkey(const std::vector<std::string> &args = std::vector<std::string>()); @@ -143,6 +143,7 @@ namespace cryptonote bool set_segregation_height(const std::vector<std::string> &args = std::vector<std::string>()); bool set_ignore_fractional_outputs(const std::vector<std::string> &args = std::vector<std::string>()); bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>()); bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool start_mining(const std::vector<std::string> &args); @@ -234,13 +235,19 @@ namespace cryptonote bool mms(const std::vector<std::string>& args); bool print_ring(const std::vector<std::string>& args); bool set_ring(const std::vector<std::string>& args); + bool unset_ring(const std::vector<std::string>& args); bool save_known_rings(const std::vector<std::string>& args); bool blackball(const std::vector<std::string>& args); bool unblackball(const std::vector<std::string>& args); bool blackballed(const std::vector<std::string>& args); + bool freeze(const std::vector<std::string>& args); + bool thaw(const std::vector<std::string>& args); + bool frozen(const std::vector<std::string>& args); + bool net_stats(const std::vector<std::string>& args); + bool welcome(const std::vector<std::string>& args); bool version(const std::vector<std::string>& args); - bool cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func); + bool cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr); bool ask_wallet_create_if_needed(); @@ -253,6 +260,7 @@ namespace cryptonote void key_images_sync_intern(); void on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money); std::pair<std::string, std::string> show_outputs_line(const std::vector<uint64_t> &heights, uint64_t blockchain_height, uint64_t highlight_height = std::numeric_limits<uint64_t>::max()) const; + bool freeze_thaw(const std::vector<std::string>& args, bool freeze); struct transfer_view { @@ -293,6 +301,13 @@ namespace cryptonote */ void commit_or_save(std::vector<tools::wallet2::pending_tx>& ptx_vector, bool do_not_relay); + /*! + * \brief checks whether background mining is enabled, and asks to configure it if not + */ + void check_background_mining(const epee::wipeable_string &password); + void start_background_mining(); + void stop_background_mining(); + //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index); diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index def23aff0..d0fc21f51 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -127,6 +127,7 @@ if (BUILD_GUI_DEPS) ringct_basic checkpoints version + net device_trezor) foreach(lib ${libs_to_merge}) diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 82986ba2d..032b873d6 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -249,6 +249,13 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } + virtual void on_device_button_pressed() + { + if (m_listener) { + m_listener->onDeviceButtonPressed(); + } + } + virtual boost::optional<epee::wipeable_string> on_device_pin_request() { if (m_listener) { @@ -449,6 +456,11 @@ WalletImpl::~WalletImpl() close(false); // do not store wallet as part of the closing activities // Stop refresh thread stopRefresh(); + + if (m_wallet2Callback->getListener()) { + m_wallet2Callback->getListener()->onSetWallet(nullptr); + } + LOG_PRINT_L1(__FUNCTION__ << " finished"); } @@ -938,7 +950,7 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ { try { - cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response response; + tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::response response; if(!m_wallet->light_wallet_import_wallet_request(response)){ setStatusError(tr("Failed to send import wallet request")); return false; @@ -2173,7 +2185,7 @@ void WalletImpl::pendingTxPostProcess(PendingTransactionImpl * pending) bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl) { - if (!m_wallet->init(daemon_address, m_daemon_login, tcp::endpoint{}, upper_transaction_size_limit)) + if (!m_wallet->init(daemon_address, m_daemon_login, boost::asio::ip::tcp::endpoint{}, upper_transaction_size_limit)) return false; // in case new wallet, this will force fast-refresh (pulling hashes instead of blocks) diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index ee1d6ae79..0af3b1867 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -37,6 +37,7 @@ #include <set> #include <ctime> #include <iostream> +#include <stdexcept> // Public interface for libwallet library namespace Monero { @@ -337,6 +338,7 @@ protected: bool m_indeterminate; }; +struct Wallet; struct WalletListener { virtual ~WalletListener() = 0; @@ -381,7 +383,12 @@ struct WalletListener /** * @brief called by device if the action is required */ - virtual void onDeviceButtonRequest(uint64_t code) {} + virtual void onDeviceButtonRequest(uint64_t code) { (void)code; } + + /** + * @brief called by device if the button was pressed + */ + virtual void onDeviceButtonPressed() { } /** * @brief called by device when PIN is needed @@ -401,7 +408,12 @@ struct WalletListener /** * @brief Signalizes device operation progress */ - virtual void onDeviceProgress(const DeviceProgress & event) {}; + virtual void onDeviceProgress(const DeviceProgress & event) { (void)event; }; + + /** + * @brief If the listener is created before the wallet this enables to set created wallet object + */ + virtual void onSetWallet(Wallet * wallet) { (void)wallet; }; }; @@ -440,8 +452,8 @@ struct Wallet //! returns both error and error string atomically. suggested to use in instead of status() and errorString() virtual void statusWithErrorString(int& status, std::string& errorString) const = 0; virtual bool setPassword(const std::string &password) = 0; - virtual bool setDevicePin(const std::string &password) { return false; }; - virtual bool setDevicePassphrase(const std::string &password) { return false; }; + virtual bool setDevicePin(const std::string &pin) { (void)pin; return false; }; + virtual bool setDevicePassphrase(const std::string &passphrase) { (void)passphrase; return false; }; virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const = 0; std::string mainAddress() const { return address(0, 0); } virtual std::string path() const = 0; @@ -1020,9 +1032,10 @@ struct WalletManager * \param password Password of wallet file * \param nettype Network type * \param kdf_rounds Number of rounds for key derivation function + * \param listener Wallet listener to set to the wallet after creation * \return Wallet instance (Wallet::status() needs to be called to check if opened successfully) */ - virtual Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1) = 0; + virtual Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, WalletListener * listener = nullptr) = 0; Wallet * openWallet(const std::string &path, const std::string &password, bool testnet = false) // deprecated { return openWallet(path, password, testnet ? TESTNET : MAINNET); @@ -1134,6 +1147,7 @@ struct WalletManager * \param restoreHeight restore from start height (0 sets to current height) * \param subaddressLookahead Size of subaddress lookahead (empty sets to some default low value) * \param kdf_rounds Number of rounds for key derivation function + * \param listener Wallet listener to set to the wallet after creation * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ virtual Wallet * createWalletFromDevice(const std::string &path, @@ -1142,7 +1156,8 @@ struct WalletManager const std::string &deviceName, uint64_t restoreHeight = 0, const std::string &subaddressLookahead = "", - uint64_t kdf_rounds = 1) = 0; + uint64_t kdf_rounds = 1, + WalletListener * listener = nullptr) = 0; /*! * \brief Closes wallet. In case operation succeeded, wallet object deleted. in case operation failed, wallet object not deleted diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index f584e88ac..ef2ed2015 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -57,9 +57,14 @@ Wallet *WalletManagerImpl::createWallet(const std::string &path, const std::stri return wallet; } -Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds) +Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds, WalletListener * listener) { WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); + wallet->setListener(listener); + if (listener){ + listener->onSetWallet(wallet); + } + wallet->open(path, password); //Refresh addressBook wallet->addressBook()->refresh(); @@ -122,9 +127,15 @@ Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, const std::string &deviceName, uint64_t restoreHeight, const std::string &subaddressLookahead, - uint64_t kdf_rounds) + uint64_t kdf_rounds, + WalletListener * listener) { WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); + wallet->setListener(listener); + if (listener){ + listener->onSetWallet(wallet); + } + if(restoreHeight > 0){ wallet->setRefreshFromBlockHeight(restoreHeight); } else { diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 0c83d794f..235f96e17 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -40,7 +40,7 @@ class WalletManagerImpl : public WalletManager public: Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype, uint64_t kdf_rounds = 1) override; - Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1) override; + Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, WalletListener * listener = nullptr) override; virtual Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, @@ -72,7 +72,8 @@ public: const std::string &deviceName, uint64_t restoreHeight = 0, const std::string &subaddressLookahead = "", - uint64_t kdf_rounds = 1) override; + uint64_t kdf_rounds = 1, + WalletListener * listener = nullptr) override; 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; diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index b69022af4..8da95de7b 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -281,7 +281,7 @@ bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::t return true; } -bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) +bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images) { MDB_txn *txn; int dbr; @@ -294,17 +294,10 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote 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; - for (const auto &in: tx.vin) + for (const crypto::key_image &key_image: key_images) { - if (in.type() != typeid(cryptonote::txin_to_key)) - continue; - const auto &txin = boost::get<cryptonote::txin_to_key>(in); - const uint32_t ring_size = txin.key_offsets.size(); - if (ring_size == 1) - continue; - MDB_val key, data; - std::string key_ciphertext = encrypt(txin.k_image, chacha_key); + std::string key_ciphertext = encrypt(key_image, chacha_key); key.mv_data = (void*)key_ciphertext.data(); key.mv_size = key_ciphertext.size(); @@ -314,7 +307,7 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote continue; THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); - MDEBUG("Removing ring data for key image " << txin.k_image); + MDEBUG("Removing ring data for key image " << key_image); dbr = mdb_del(txn, dbi_rings, &key, NULL); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to remove ring to database: " + std::string(mdb_strerror(dbr))); } @@ -325,6 +318,23 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote return true; } +bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) +{ + std::vector<crypto::key_image> key_images; + key_images.reserve(tx.vin.size()); + for (const auto &in: tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + const auto &txin = boost::get<cryptonote::txin_to_key>(in); + const uint32_t ring_size = txin.key_offsets.size(); + if (ring_size == 1) + continue; + key_images.push_back(txin.k_image); + } + return remove_rings(chacha_key, key_images); +} + bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs) { MDB_txn *txn; diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 7b448b0d7..9c7e624bc 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -45,6 +45,7 @@ namespace tools ~ringdb(); bool add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); + bool remove_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images); bool remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 9ba5f9946..0a98c13ac 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -243,9 +243,10 @@ struct options { const command_line::arg_descriptor<std::string> daemon_ssl = {"daemon-ssl", tools::wallet2::tr("Enable SSL on daemon RPC connections: enabled|disabled|autodetect"), "autodetect"}; const command_line::arg_descriptor<std::string> daemon_ssl_private_key = {"daemon-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""}; const command_line::arg_descriptor<std::string> daemon_ssl_certificate = {"daemon-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""}; - const command_line::arg_descriptor<std::vector<std::string>> daemon_ssl_allowed_certificates = {"daemon-ssl-allowed-certificates", tools::wallet2::tr("List of paths to PEM format certificates of allowed RPC servers")}; + const command_line::arg_descriptor<std::string> daemon_ssl_ca_certificates = {"daemon-ssl-ca-certificates", tools::wallet2::tr("Path to file containing concatenated PEM format certificate(s) to replace system CA(s).")}; const command_line::arg_descriptor<std::vector<std::string>> daemon_ssl_allowed_fingerprints = {"daemon-ssl-allowed-fingerprints", tools::wallet2::tr("List of valid fingerprints of allowed RPC servers")}; const command_line::arg_descriptor<bool> daemon_ssl_allow_any_cert = {"daemon-ssl-allow-any-cert", tools::wallet2::tr("Allow any SSL certificate from the daemon"), false}; + const command_line::arg_descriptor<bool> daemon_ssl_allow_chained = {"daemon-ssl-allow-chained", tools::wallet2::tr("Allow user (via --daemon-ssl-ca-certificates) chain certificates"), false}; const command_line::arg_descriptor<bool> testnet = {"testnet", tools::wallet2::tr("For testnet. Daemon must also be launched with --testnet flag"), false}; const command_line::arg_descriptor<bool> stagenet = {"stagenet", tools::wallet2::tr("For stagenet. Daemon must also be launched with --stagenet flag"), false}; const command_line::arg_descriptor<std::string, false, true, 2> shared_ringdb_dir = { @@ -314,6 +315,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl const uint64_t kdf_rounds = command_line::get_arg(vm, opts.kdf_rounds); THROW_WALLET_EXCEPTION_IF(kdf_rounds == 0, tools::error::wallet_internal_error, "KDF rounds must not be 0"); + const bool use_proxy = command_line::has_arg(vm, opts.proxy); auto daemon_address = command_line::get_arg(vm, opts.daemon_address); auto daemon_host = command_line::get_arg(vm, opts.daemon_host); auto daemon_port = command_line::get_arg(vm, opts.daemon_port); @@ -321,13 +323,37 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl auto device_derivation_path = command_line::get_arg(vm, opts.hw_device_derivation_path); auto daemon_ssl_private_key = command_line::get_arg(vm, opts.daemon_ssl_private_key); auto daemon_ssl_certificate = command_line::get_arg(vm, opts.daemon_ssl_certificate); - auto daemon_ssl_allowed_certificates = command_line::get_arg(vm, opts.daemon_ssl_allowed_certificates); + auto daemon_ssl_ca_file = command_line::get_arg(vm, opts.daemon_ssl_ca_certificates); auto daemon_ssl_allowed_fingerprints = command_line::get_arg(vm, opts.daemon_ssl_allowed_fingerprints); auto daemon_ssl_allow_any_cert = command_line::get_arg(vm, opts.daemon_ssl_allow_any_cert); auto daemon_ssl = command_line::get_arg(vm, opts.daemon_ssl); - epee::net_utils::ssl_support_t ssl_support; - THROW_WALLET_EXCEPTION_IF(!epee::net_utils::ssl_support_from_string(ssl_support, daemon_ssl), tools::error::wallet_internal_error, - tools::wallet2::tr("Invalid argument for ") + std::string(opts.daemon_ssl.name)); + + // user specified CA file or fingeprints implies enabled SSL by default + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + if (command_line::get_arg(vm, opts.daemon_ssl_allow_any_cert)) + ssl_options.verification = epee::net_utils::ssl_verification_t::none; + else if (!daemon_ssl_ca_file.empty() || !daemon_ssl_allowed_fingerprints.empty()) + { + std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ daemon_ssl_allowed_fingerprints.size() }; + std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); + + ssl_options = epee::net_utils::ssl_options_t{ + std::move(ssl_allowed_fingerprints), std::move(daemon_ssl_ca_file) + }; + + if (command_line::get_arg(vm, opts.daemon_ssl_allow_chained)) + ssl_options.verification = epee::net_utils::ssl_verification_t::user_ca; + } + + if (ssl_options.verification != epee::net_utils::ssl_verification_t::user_certificates || !command_line::is_arg_defaulted(vm, opts.daemon_ssl)) + { + THROW_WALLET_EXCEPTION_IF(!epee::net_utils::ssl_support_from_string(ssl_options.support, daemon_ssl), tools::error::wallet_internal_error, + tools::wallet2::tr("Invalid argument for ") + std::string(opts.daemon_ssl.name)); + } + + ssl_options.auth = epee::net_utils::ssl_authentication_t{ + std::move(daemon_ssl_private_key), std::move(daemon_ssl_certificate) + }; THROW_WALLET_EXCEPTION_IF(!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port, tools::error::wallet_internal_error, tools::wallet2::tr("can't specify daemon host or port more than once")); @@ -357,22 +383,24 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl if (daemon_address.empty()) daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); - boost::asio::ip::tcp::endpoint proxy{}; - if (command_line::has_arg(vm, opts.proxy)) { - namespace ip = boost::asio::ip; const boost::string_ref real_daemon = boost::string_ref{daemon_address}.substr(0, daemon_address.rfind(':')); - // onion and i2p addresses contain information about the server cert - // which both authenticates and encrypts - const bool unencrypted_proxy = - !real_daemon.ends_with(".onion") && !real_daemon.ends_with(".i2p") && - daemon_ssl_allowed_certificates.empty() && daemon_ssl_allowed_fingerprints.empty(); + const bool verification_required = + ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_enabled || use_proxy; + THROW_WALLET_EXCEPTION_IF( - unencrypted_proxy, + verification_required && !ssl_options.has_strong_verification(real_daemon), tools::error::wallet_internal_error, - std::string{"Use of --"} + opts.proxy.name + " requires --" + opts.daemon_ssl_allowed_certificates.name + " or --" + opts.daemon_ssl_allowed_fingerprints.name + " or use of a .onion/.i2p domain" + tools::wallet2::tr("Enabling --") + std::string{use_proxy ? opts.proxy.name : opts.daemon_ssl.name} + tools::wallet2::tr(" requires --") + + opts.daemon_ssl_ca_certificates.name + tools::wallet2::tr(" or --") + opts.daemon_ssl_allowed_fingerprints.name + tools::wallet2::tr(" or use of a .onion/.i2p domain") ); + } + + boost::asio::ip::tcp::endpoint proxy{}; + if (use_proxy) + { + namespace ip = boost::asio::ip; const auto proxy_address = command_line::get_arg(vm, opts.proxy); @@ -416,22 +444,8 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl catch (const std::exception &e) { } } - std::list<std::string> ssl_allowed_certificates; - for (const std::string &path: daemon_ssl_allowed_certificates) - { - ssl_allowed_certificates.push_back({}); - if (!epee::file_io_utils::load_file_to_string(path, ssl_allowed_certificates.back())) - { - MERROR("Failed to load certificate: " << path); - ssl_allowed_certificates.back() = std::string(); - } - } - - std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ daemon_ssl_allowed_fingerprints.size() }; - std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); - std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds, unattended)); - wallet->init(std::move(daemon_address), std::move(login), std::move(proxy), 0, *trusted_daemon, ssl_support, std::make_pair(daemon_ssl_private_key, daemon_ssl_certificate), ssl_allowed_certificates, ssl_allowed_fingerprints, daemon_ssl_allow_any_cert); + wallet->init(std::move(daemon_address), std::move(login), std::move(proxy), 0, *trusted_daemon, std::move(ssl_options)); boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); wallet->set_ring_database(ringdb_path.string()); wallet->get_message_store().set_options(vm); @@ -512,7 +526,7 @@ std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_f GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, filename, std::string, String, true, std::string()); GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, scan_from_height, uint64_t, Uint64, false, 0); - const bool recover = field_scan_from_height_found; + const bool recover = true; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, password, std::string, String, false, std::string()); @@ -622,6 +636,8 @@ std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_f wallet.reset(make_basic(vm, unattended, opts, password_prompter).release()); wallet->set_refresh_from_block_height(field_scan_from_height); wallet->explicit_refresh_from_block_height(field_scan_from_height_found); + if (!old_language.empty()) + wallet->set_seed_language(old_language); try { @@ -976,6 +992,12 @@ void wallet_device_callback::on_button_request(uint64_t code) wallet->on_device_button_request(code); } +void wallet_device_callback::on_button_pressed() +{ + if (wallet) + wallet->on_device_button_pressed(); +} + boost::optional<epee::wipeable_string> wallet_device_callback::on_pin_request() { if (wallet) @@ -1030,6 +1052,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_segregation_height(0), m_ignore_fractional_outputs(true), m_track_uses(false), + m_setup_background_mining(BackgroundMiningMaybe), m_is_initialized(false), m_kdf_rounds(kdf_rounds), is_old_file_format(false), @@ -1098,9 +1121,10 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.daemon_ssl); command_line::add_arg(desc_params, opts.daemon_ssl_private_key); command_line::add_arg(desc_params, opts.daemon_ssl_certificate); - command_line::add_arg(desc_params, opts.daemon_ssl_allowed_certificates); + command_line::add_arg(desc_params, opts.daemon_ssl_ca_certificates); command_line::add_arg(desc_params, opts.daemon_ssl_allowed_fingerprints); command_line::add_arg(desc_params, opts.daemon_ssl_allow_any_cert); + command_line::add_arg(desc_params, opts.daemon_ssl_allow_chained); command_line::add_arg(desc_params, opts.testnet); command_line::add_arg(desc_params, opts.stagenet); command_line::add_arg(desc_params, opts.shared_ringdb_dir); @@ -1152,21 +1176,26 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia } //---------------------------------------------------------------------------------------------------- -bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, boost::asio::ip::tcp::endpoint proxy, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, const std::vector<std::vector<uint8_t>> &allowed_fingerprints, bool allow_any_cert) +bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options) { - m_checkpoints.init_default_checkpoints(m_nettype); if(m_http_client.is_connected()) m_http_client.disconnect(); - m_is_initialized = true; - m_upper_transaction_weight_limit = upper_transaction_weight_limit; m_daemon_address = std::move(daemon_address); m_daemon_login = std::move(daemon_login); m_trusted_daemon = trusted_daemon; + + MINFO("setting daemon to " << get_daemon_address()); + return m_http_client.set_server(get_daemon_address(), get_daemon_login(), std::move(ssl_options)); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, boost::asio::ip::tcp::endpoint proxy, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options) +{ + m_checkpoints.init_default_checkpoints(m_nettype); + m_is_initialized = true; + m_upper_transaction_weight_limit = upper_transaction_weight_limit; if (proxy != boost::asio::ip::tcp::endpoint{}) m_http_client.set_connector(net::socks::connector{std::move(proxy)}); - - // When switching from light wallet to full wallet, we need to reset the height we got from lw node. - return m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl_support, private_key_and_certificate_path, allowed_certificates, allowed_fingerprints, allow_any_cert); + return set_daemon(daemon_address, daemon_login, trusted_daemon, std::move(ssl_options)); } //---------------------------------------------------------------------------------------------------- bool wallet2::is_deterministic() const @@ -1439,6 +1468,58 @@ void wallet2::set_unspent(size_t idx) td.m_spent_height = 0; } //---------------------------------------------------------------------------------------------------- +void wallet2::freeze(size_t idx) +{ + CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "Invalid transfer_details index"); + transfer_details &td = m_transfers[idx]; + td.m_frozen = true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::thaw(size_t idx) +{ + CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "Invalid transfer_details index"); + transfer_details &td = m_transfers[idx]; + td.m_frozen = false; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::frozen(size_t idx) const +{ + CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "Invalid transfer_details index"); + const transfer_details &td = m_transfers[idx]; + return td.m_frozen; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::freeze(const crypto::key_image &ki) +{ + freeze(get_transfer_details(ki)); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::thaw(const crypto::key_image &ki) +{ + thaw(get_transfer_details(ki)); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::frozen(const crypto::key_image &ki) const +{ + return frozen(get_transfer_details(ki)); +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::get_transfer_details(const crypto::key_image &ki) const +{ + for (size_t idx = 0; idx < m_transfers.size(); ++idx) + { + const transfer_details &td = m_transfers[idx]; + if (td.m_key_image_known && td.m_key_image == ki) + return idx; + } + CHECK_AND_ASSERT_THROW_MES(false, "Key image not found"); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::frozen(const transfer_details &td) const +{ + return td.m_frozen; +} +//---------------------------------------------------------------------------------------------------- void wallet2::check_acc_out_precomp(const tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const { hw::device &hwdev = m_account.get_device(); @@ -1856,6 +1937,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_mask = rct::identity(); td.m_rct = false; } + td.m_frozen = false; set_unspent(m_transfers.size()-1); if (td.m_key_image_known) m_key_images[td.m_key_image] = m_transfers.size()-1; @@ -2212,6 +2294,12 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans add_rings(tx); } //---------------------------------------------------------------------------------------------------- +bool wallet2::should_skip_block(const cryptonote::block &b, uint64_t height) const +{ + // seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup + return !(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height); +} +//---------------------------------------------------------------------------------------------------- void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache) { THROW_WALLET_EXCEPTION_IF(bche.txs.size() + 1 != parsed_block.o_indices.indices.size(), error::wallet_internal_error, @@ -2221,7 +2309,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry //handle transactions from new block //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup - if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) + if (!should_skip_block(b, height)) { TIME_MEASURE_START(miner_tx_handle_time); if (m_refresh_type != RefreshNoCoinbase) @@ -2285,9 +2373,7 @@ void wallet2::get_short_chain_history(std::list<crypto::hash>& ids, uint64_t gra //---------------------------------------------------------------------------------------------------- void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const { - error = !cryptonote::parse_and_validate_block_from_blob(blob, bl); - if (!error) - bl_id = get_block_hash(bl); + error = !cryptonote::parse_and_validate_block_from_blob(blob, bl, bl_id); } //---------------------------------------------------------------------------------------------------- void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices) @@ -2353,6 +2439,11 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry { THROW_WALLET_EXCEPTION_IF(parsed_blocks[i].txes.size() != parsed_blocks[i].block.tx_hashes.size(), error::wallet_internal_error, "Mismatched parsed_blocks[i].txes.size() and parsed_blocks[i].block.tx_hashes.size()"); + if (should_skip_block(parsed_blocks[i].block, start_height + i)) + { + txidx += 1 + parsed_blocks[i].block.tx_hashes.size(); + continue; + } if (m_refresh_type != RefreshNoCoinbase) tpool.submit(&waiter, [&, i, txidx](){ cache_tx_data(parsed_blocks[i].block.miner_tx, get_transaction_hash(parsed_blocks[i].block.miner_tx), tx_cache_data[txidx]); }); ++txidx; @@ -2381,6 +2472,8 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry for (size_t i = 0; i < tx_cache_data.size(); ++i) { + if (tx_cache_data[i].empty()) + continue; tpool.submit(&waiter, [&hwdev, &gender, &tx_cache_data, i]() { auto &slot = tx_cache_data[i]; boost::unique_lock<hw::device> hwdev_lock(hwdev); @@ -2399,6 +2492,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry if (o.target.type() == typeid(cryptonote::txout_to_key)) { std::vector<crypto::key_derivation> additional_derivations; + additional_derivations.reserve(tx_cache_data[txidx].additional.size()); for (const auto &iod: tx_cache_data[txidx].additional) additional_derivations.push_back(iod.derivation); const auto &key = boost::get<txout_to_key>(o.target).key; @@ -2416,6 +2510,12 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry txidx = 0; for (size_t i = 0; i < blocks.size(); ++i) { + if (should_skip_block(parsed_blocks[i].block, start_height + i)) + { + txidx += 1 + parsed_blocks[i].block.tx_hashes.size(); + continue; + } + if (m_refresh_type != RefreshType::RefreshNoCoinbase) { THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range"); @@ -2453,7 +2553,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry " (height " + std::to_string(start_height) + "), local block id at this height: " + string_tools::pod_to_hex(m_blockchain[current_index])); - detach_blockchain(current_index); + detach_blockchain(current_index, output_tracker_cache); process_new_blockchain_entry(bl, blocks[i], parsed_blocks[i], bl_id, current_index, tx_cache_data, tx_cache_data_offset, output_tracker_cache); } else @@ -2897,7 +2997,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo // MyMonero get_address_info needs to be called occasionally to trigger wallet sync. // This call is not really needed for other purposes and can be removed if mymonero changes their backend. - cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response res; + tools::COMMAND_RPC_GET_ADDRESS_INFO::response res; // Get basic info if(light_wallet_get_address_info(res)) { @@ -3045,7 +3145,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo // if we've got at least 10 blocks to refresh, assume we're starting // a long refresh, and setup a tracking output cache if we need to - if (m_track_uses && !output_tracker_cache && next_blocks.size() >= 10) + if (m_track_uses && (!output_tracker_cache || output_tracker_cache->empty()) && next_blocks.size() >= 10) output_tracker_cache = create_output_tracker_cache(); // switch to the new blocks from the daemon @@ -3186,7 +3286,7 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> return true; } //---------------------------------------------------------------------------------------------------- -void wallet2::detach_blockchain(uint64_t height) +void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache) { LOG_PRINT_L0("Detaching blockchain on height " << height); @@ -3203,11 +3303,21 @@ void wallet2::detach_blockchain(uint64_t height) wallet2::transfer_details &td = m_transfers[i]; if (td.m_spent && td.m_spent_height >= height) { - LOG_PRINT_L1("Resetting spent status for output " << i << ": " << td.m_key_image); + LOG_PRINT_L1("Resetting spent/frozen status for output " << i << ": " << td.m_key_image); set_unspent(i); + thaw(i); } } + for (transfer_details &td: m_transfers) + { + while (!td.m_uses.empty() && td.m_uses.back().first >= height) + td.m_uses.pop_back(); + } + + if (output_tracker_cache) + output_tracker_cache->clear(); + auto it = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_details& td){return td.m_block_height >= height;}); size_t i_start = it - m_transfers.begin(); @@ -3449,6 +3559,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetInt(m_track_uses ? 1 : 0); json.AddMember("track_uses", value2, json.GetAllocator()); + value2.SetInt(m_setup_background_mining); + json.AddMember("setup_background_mining", value2, json.GetAllocator()); + value2.SetUint(m_subaddress_lookahead_major); json.AddMember("subaddress_lookahead_major", value2, json.GetAllocator()); @@ -3537,7 +3650,8 @@ void wallet2::change_password(const std::string &filename, const epee::wipeable_ decrypt_keys(original_password); setup_keys(new_password); rewrite(filename, new_password); - store(); + if (!filename.empty()) + store(); } //---------------------------------------------------------------------------------------------------- /*! @@ -3577,13 +3691,16 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_multisig_derivations.clear(); m_always_confirm_transfers = false; m_print_ring_members = false; + m_store_tx_info = true; m_default_mixin = 0; m_default_priority = 0; m_auto_refresh = true; m_refresh_type = RefreshType::RefreshDefault; + m_refresh_from_block_height = 0; m_confirm_missing_payment_id = true; m_confirm_non_default_ring_size = true; m_ask_password = AskPasswordToDecrypt; + cryptonote::set_default_decimal_point(CRYPTONOTE_DISPLAY_DECIMAL_POINT); m_min_output_count = 0; m_min_output_value = 0; m_merge_destinations = false; @@ -3596,6 +3713,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_segregation_height = 0; m_ignore_fractional_outputs = true; m_track_uses = false; + m_setup_background_mining = BackgroundMiningMaybe; m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; m_original_keys_available = false; @@ -3750,6 +3868,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_ignore_fractional_outputs = field_ignore_fractional_outputs; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false); m_track_uses = field_track_uses; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, setup_background_mining, BackgroundMiningSetupType, Int, false, BackgroundMiningMaybe); + m_setup_background_mining = field_setup_background_mining; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_major, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MAJOR); m_subaddress_lookahead_major = field_subaddress_lookahead_major; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR); @@ -4051,6 +4171,17 @@ bool wallet2::query_device(hw::device::device_type& device_type, const std::stri return true; } +void wallet2::init_type(hw::device::device_type device_type) +{ + m_account_public_address = m_account.get_keys().m_account_address; + m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_signers.clear(); + m_original_keys_available = false; + m_key_device_type = device_type; +} + /*! * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file @@ -4120,18 +4251,15 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); m_account.finalize_multisig(spend_public_key); - m_account_public_address = m_account.get_keys().m_account_address; - m_watch_only = false; + // Not possible to restore a multisig wallet that is able to activate the MMS + // (because the original keys are not (yet) part of the restore info), so + // keep m_original_keys_available to false + init_type(hw::device::device_type::SOFTWARE); m_multisig = true; m_multisig_threshold = threshold; m_multisig_signers = multisig_signers; - m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); - // Not possible to restore a multisig wallet that is able to activate the MMS - // (because the original keys are not (yet) part of the restore info) - m_original_keys_available = false; - create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); setup_new_blockchain(); @@ -4164,13 +4292,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip crypto::secret_key retval = m_account.generate(recovery_param, recover, two_random); - m_account_public_address = m_account.get_keys().m_account_address; - m_watch_only = false; - m_multisig = false; - m_multisig_threshold = 0; - m_multisig_signers.clear(); - m_original_keys_available = false; - m_key_device_type = hw::device::device_type::SOFTWARE; + init_type(hw::device::device_type::SOFTWARE); setup_keys(password); // calculate a starting refresh height @@ -4253,13 +4375,9 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& } m_account.create_from_viewkey(account_public_address, viewkey); - m_account_public_address = account_public_address; + init_type(hw::device::device_type::SOFTWARE); m_watch_only = true; - m_multisig = false; - m_multisig_threshold = 0; - m_multisig_signers.clear(); - m_original_keys_available = false; - m_key_device_type = hw::device::device_type::SOFTWARE; + m_account_public_address = account_public_address; setup_keys(password); create_keys_file(wallet_, true, password, m_nettype != MAINNET || create_address_file); @@ -4294,13 +4412,8 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& } m_account.create_from_keys(account_public_address, spendkey, viewkey); + init_type(hw::device::device_type::SOFTWARE); m_account_public_address = account_public_address; - m_watch_only = false; - m_multisig = false; - m_multisig_threshold = 0; - m_multisig_signers.clear(); - m_original_keys_available = false; - m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); create_keys_file(wallet_, false, password, create_address_file); @@ -4335,13 +4448,7 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p hwdev.set_callback(get_device_callback()); 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; - m_multisig_threshold = 0; - m_multisig_signers.clear(); - m_original_keys_available = false; + init_type(m_account.get_device().get_type()); setup_keys(password); m_device_name = device_name; @@ -4473,10 +4580,9 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, "Failed to create multisig wallet due to bad keys"); memwipe(&spend_skey, sizeof(rct::key)); - m_account_public_address = m_account.get_keys().m_account_address; - m_watch_only = false; + init_type(hw::device::device_type::SOFTWARE); + m_original_keys_available = true; m_multisig = true; - m_key_device_type = hw::device::device_type::SOFTWARE; m_multisig_threshold = threshold; m_multisig_signers = multisig_signers; ++m_multisig_rounds_passed; @@ -5226,7 +5332,8 @@ std::string wallet2::path() const //---------------------------------------------------------------------------------------------------- void wallet2::store() { - store_to("", epee::wipeable_string()); + if (!m_wallet_file.empty()) + store_to("", epee::wipeable_string()); } //---------------------------------------------------------------------------------------------------- void wallet2::store_to(const std::string &path, const epee::wipeable_string &password) @@ -5363,13 +5470,19 @@ uint64_t wallet2::balance(uint32_t index_major) const return amount; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::unlocked_balance(uint32_t index_major) const +uint64_t wallet2::unlocked_balance(uint32_t index_major, uint64_t *blocks_to_unlock) const { uint64_t amount = 0; + if (blocks_to_unlock) + *blocks_to_unlock = 0; if(m_light_wallet) return m_light_wallet_balance; for (const auto& i : unlocked_balance_per_subaddress(index_major)) - amount += i.second; + { + amount += i.second.first; + if (blocks_to_unlock && i.second.second > *blocks_to_unlock) + *blocks_to_unlock = i.second.second; + } return amount; } //---------------------------------------------------------------------------------------------------- @@ -5378,7 +5491,7 @@ std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_majo std::map<uint32_t, uint64_t> amount_per_subaddr; for (const auto& td: m_transfers) { - if (td.m_subaddr_index.major == index_major && !td.m_spent) + if (td.m_subaddr_index.major == index_major && !td.m_spent && !td.m_frozen) { auto found = amount_per_subaddr.find(td.m_subaddr_index.minor); if (found == amount_per_subaddr.end()) @@ -5402,18 +5515,36 @@ std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_majo return amount_per_subaddr; } //---------------------------------------------------------------------------------------------------- -std::map<uint32_t, uint64_t> wallet2::unlocked_balance_per_subaddress(uint32_t index_major) const +std::map<uint32_t, std::pair<uint64_t, uint64_t>> wallet2::unlocked_balance_per_subaddress(uint32_t index_major) const { - std::map<uint32_t, uint64_t> amount_per_subaddr; + std::map<uint32_t, std::pair<uint64_t, uint64_t>> amount_per_subaddr; + const uint64_t blockchain_height = get_blockchain_current_height(); for(const transfer_details& td: m_transfers) { - if(td.m_subaddr_index.major == index_major && !td.m_spent && is_transfer_unlocked(td)) + if(td.m_subaddr_index.major == index_major && !td.m_spent && !td.m_frozen) { + uint64_t amount = 0, blocks_to_unlock = 0; + if (is_transfer_unlocked(td)) + { + amount = td.amount(); + blocks_to_unlock = 0; + } + else + { + uint64_t unlock_height = td.m_block_height + std::max<uint64_t>(CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS); + if (td.m_tx.unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && td.m_tx.unlock_time > unlock_height) + unlock_height = td.m_tx.unlock_time; + blocks_to_unlock = unlock_height > blockchain_height ? unlock_height - blockchain_height : 0; + amount = 0; + } auto found = amount_per_subaddr.find(td.m_subaddr_index.minor); if (found == amount_per_subaddr.end()) - amount_per_subaddr[td.m_subaddr_index.minor] = td.amount(); + amount_per_subaddr[td.m_subaddr_index.minor] = std::make_pair(amount, blocks_to_unlock); else - found->second += td.amount(); + { + found->second.first += amount; + found->second.second = std::max(found->second.second, blocks_to_unlock); + } } } return amount_per_subaddr; @@ -5427,11 +5558,18 @@ uint64_t wallet2::balance_all() const return r; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::unlocked_balance_all() const +uint64_t wallet2::unlocked_balance_all(uint64_t *blocks_to_unlock) const { uint64_t r = 0; + if (blocks_to_unlock) + *blocks_to_unlock = 0; for (uint32_t index_major = 0; index_major < get_num_subaddress_accounts(); ++index_major) - r += unlocked_balance(index_major); + { + uint64_t local_blocks_to_unlock; + r += unlocked_balance(index_major, blocks_to_unlock ? &local_blocks_to_unlock : NULL); + if (blocks_to_unlock) + *blocks_to_unlock = std::max(*blocks_to_unlock, local_blocks_to_unlock); + } return r; } //---------------------------------------------------------------------------------------------------- @@ -6962,6 +7100,43 @@ bool wallet2::set_ring(const crypto::key_image &key_image, const std::vector<uin catch (const std::exception &e) { return false; } } +bool wallet2::unset_ring(const std::vector<crypto::key_image> &key_images) +{ + if (!m_ringdb) + return false; + + try { return m_ringdb->remove_rings(get_ringdb_key(), key_images); } + catch (const std::exception &e) { return false; } +} + +bool wallet2::unset_ring(const crypto::hash &txid) +{ + if (!m_ringdb) + return false; + + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + req.prune = true; + m_daemon_rpc_mutex.lock(); + bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to get transaction from daemon"); + if (res.txs.empty()) + return false; + THROW_WALLET_EXCEPTION_IF(res.txs.size(), error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::transaction tx; + crypto::hash tx_hash; + if (!get_pruned_tx(res.txs.front(), tx, tx_hash)) + return false; + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + try { return m_ringdb->remove_rings(get_ringdb_key(), tx); } + catch (const std::exception &e) { return false; } +} + bool wallet2::find_and_save_rings(bool force) { if (!force && m_ring_history_saved) @@ -8301,7 +8476,7 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_frozen && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount())); picks.push_back(i); @@ -8316,13 +8491,13 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_frozen && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount())); for (size_t j = i + 1; j < m_transfers.size(); ++j) { const transfer_details& td2 = m_transfers[j]; - if (!td2.m_spent && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) + if (!td2.m_spent && !td2.m_frozen && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) { // update our picks if those outputs are less related than any we // already found. If the same, don't update, and oldest suitable outputs @@ -8408,8 +8583,8 @@ bool wallet2::light_wallet_login(bool &new_address) { MDEBUG("Light wallet login request"); m_light_wallet_connected = false; - cryptonote::COMMAND_RPC_LOGIN::request request; - cryptonote::COMMAND_RPC_LOGIN::response response; + tools::COMMAND_RPC_LOGIN::request request; + tools::COMMAND_RPC_LOGIN::response response; request.address = get_account().get_public_address_str(m_nettype); request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); // Always create account if it doesn't exist. @@ -8433,10 +8608,10 @@ bool wallet2::light_wallet_login(bool &new_address) return m_light_wallet_connected; } -bool wallet2::light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response) +bool wallet2::light_wallet_import_wallet_request(tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response) { MDEBUG("Light wallet import wallet request"); - cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::request oreq; + tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::request oreq; oreq.address = get_account().get_public_address_str(m_nettype); oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); m_daemon_rpc_mutex.lock(); @@ -8452,8 +8627,8 @@ void wallet2::light_wallet_get_unspent_outs() { MDEBUG("Getting unspent outs"); - cryptonote::COMMAND_RPC_GET_UNSPENT_OUTS::request oreq; - cryptonote::COMMAND_RPC_GET_UNSPENT_OUTS::response ores; + tools::COMMAND_RPC_GET_UNSPENT_OUTS::request oreq; + tools::COMMAND_RPC_GET_UNSPENT_OUTS::response ores; oreq.amount = "0"; oreq.address = get_account().get_public_address_str(m_nettype); @@ -8549,6 +8724,7 @@ void wallet2::light_wallet_get_unspent_outs() td.m_pk_index = 0; td.m_internal_output_index = o.index; td.m_spent = spent; + td.m_frozen = false; tx_out txout; txout.target = txout_to_key(public_key); @@ -8601,11 +8777,11 @@ void wallet2::light_wallet_get_unspent_outs() } } -bool wallet2::light_wallet_get_address_info(cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response &response) +bool wallet2::light_wallet_get_address_info(tools::COMMAND_RPC_GET_ADDRESS_INFO::response &response) { MTRACE(__FUNCTION__); - cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::request request; + tools::COMMAND_RPC_GET_ADDRESS_INFO::request request; request.address = get_account().get_public_address_str(m_nettype); request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); @@ -8621,8 +8797,8 @@ void wallet2::light_wallet_get_address_txs() { MDEBUG("Refreshing light wallet"); - cryptonote::COMMAND_RPC_GET_ADDRESS_TXS::request ireq; - cryptonote::COMMAND_RPC_GET_ADDRESS_TXS::response ires; + tools::COMMAND_RPC_GET_ADDRESS_TXS::request ireq; + tools::COMMAND_RPC_GET_ADDRESS_TXS::response ires; ireq.address = get_account().get_public_address_str(m_nettype); ireq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); @@ -8976,7 +9152,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // throw if attempting a transaction with no money THROW_WALLET_EXCEPTION_IF(needed_money == 0, error::zero_destination); - std::map<uint32_t, uint64_t> unlocked_balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); + std::map<uint32_t, std::pair<uint64_t, uint64_t>> unlocked_balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); std::map<uint32_t, uint64_t> balance_per_subaddr = balance_per_subaddress(subaddr_account); if (subaddr_indices.empty()) // "index=<N1>[,<N2>,...]" wasn't specified -> use all the indices with non-zero unlocked balance @@ -8994,7 +9170,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp for (uint32_t index_minor : subaddr_indices) { balance_subtotal += balance_per_subaddr[index_minor]; - unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor]; + unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor].first; } THROW_WALLET_EXCEPTION_IF(needed_money + min_fee > balance_subtotal, error::not_enough_money, balance_subtotal, needed_money, 0); @@ -9023,7 +9199,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below threshold " << print_money(fractional_threshold)); continue; } - if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_frozen && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { const uint32_t index_minor = td.m_subaddr_index.minor; auto find_predicate = [&index_minor](const std::pair<uint32_t, std::vector<size_t>>& x) { return x.first == index_minor; }; @@ -9060,7 +9236,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { auto sort_predicate = [&unlocked_balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y) { - return unlocked_balance_per_subaddr[x.first] > unlocked_balance_per_subaddr[y.first]; + return unlocked_balance_per_subaddr[x.first].first > unlocked_balance_per_subaddr[y.first].first; }; std::sort(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), sort_predicate); std::sort(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), sort_predicate); @@ -9455,7 +9631,7 @@ bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, s THROW_WALLET_EXCEPTION_IF(ptx.change_dts.addr != ptx_vector[0].change_dts.addr, error::wallet_internal_error, "Change goes to several different addresses"); const auto it = m_subaddresses.find(ptx_vector[0].change_dts.addr.m_spend_public_key); - THROW_WALLET_EXCEPTION_IF(it == m_subaddresses.end(), error::wallet_internal_error, "Change address is not ours"); + THROW_WALLET_EXCEPTION_IF(change > 0 && it == m_subaddresses.end(), error::wallet_internal_error, "Change address is not ours"); required[ptx_vector[0].change_dts.addr].first += change; required[ptx_vector[0].change_dts.addr].second = ptx_vector[0].change_dts.is_subaddress; @@ -9503,7 +9679,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && (subaddr_indices.empty() || subaddr_indices.count(td.m_subaddr_index.minor) == 1)) + if (!td.m_spent && !td.m_frozen && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && (subaddr_indices.empty() || subaddr_indices.count(td.m_subaddr_index.minor) == 1)) { fund_found = true; if (below == 0 || td.amount() < below) @@ -9551,7 +9727,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypt for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (td.m_key_image_known && td.m_key_image == ki && !td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) + if (td.m_key_image_known && td.m_key_image == ki && !td.m_spent && !td.m_frozen && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) { if (td.is_rct() || is_valid_decomposed_amount(td.amount())) unused_transfers_indices.push_back(i); @@ -9771,8 +9947,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton } uint64_t a = 0; - for (size_t idx: unused_transfers_indices) a += m_transfers[idx].amount(); - for (size_t idx: unused_dust_indices) a += m_transfers[idx].amount(); + for (const TX &tx: txes) + { + for (size_t idx: tx.selected_transfers) + { + a += m_transfers[idx].amount(); + } + a -= tx.ptx.fee; + } std::vector<cryptonote::tx_destination_entry> synthetic_dsts(1, cryptonote::tx_destination_entry("", a, address, is_subaddress)); THROW_WALLET_EXCEPTION_IF(!sanity_check(ptx_vector, synthetic_dsts), error::wallet_internal_error, "Created transaction(s) failed sanity check"); @@ -9856,7 +10038,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) const result = m_node_rpc_proxy.get_earliest_height(version, earliest_height); throw_on_rpc_response_error(result, "get_hard_fork_info"); - bool close_enough = height >= earliest_height - early_blocks && earliest_height != std::numeric_limits<uint64_t>::max(); // start using the rules that many blocks beforehand + bool close_enough = (int64_t)height >= (int64_t)earliest_height - early_blocks && earliest_height != std::numeric_limits<uint64_t>::max(); // start using the rules that many blocks beforehand if (close_enough) LOG_PRINT_L2("Using v" << (unsigned)version << " rules"); else @@ -9883,6 +10065,8 @@ std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(c { if (i->m_spent) continue; + if (i->m_frozen) + continue; if (i->m_key_image_partial) continue; if (!is_transfer_unlocked(*i)) @@ -9898,7 +10082,7 @@ std::vector<uint64_t> wallet2::get_unspent_amounts_vector() const std::set<uint64_t> set; for (const auto &td: m_transfers) { - if (!td.m_spent) + if (!td.m_spent && !td.m_frozen) set.insert(td.is_rct() ? 0 : td.amount()); } std::vector<uint64_t> vector; @@ -10024,7 +10208,7 @@ void wallet2::discard_unmixable_outputs() std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(); for (size_t idx : unmixable_outputs) { - m_transfers[idx].m_spent = true; + freeze(idx); } } @@ -10411,7 +10595,7 @@ void wallet2::check_tx_key(const crypto::hash &txid, const crypto::secret_key &t void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received) const { received = 0; - hw::device &hwdev = m_account.get_device(); + for (size_t n = 0; n < tx.vout.size(); ++n) { const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[n].target)); @@ -10419,13 +10603,13 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt continue; crypto::public_key derived_out_key; - bool r = hwdev.derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key); + bool r = crypto::derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); bool found = out_key->key == derived_out_key; crypto::key_derivation found_derivation = derivation; if (!found && !additional_derivations.empty()) { - r = hwdev.derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key); + r = crypto::derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); found = out_key->key == derived_out_key; found_derivation = additional_derivations[n]; @@ -10441,9 +10625,9 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt else { crypto::secret_key scalar1; - hwdev.derivation_to_scalar(found_derivation, n, scalar1); + crypto::derivation_to_scalar(found_derivation, n, scalar1); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - hwdev.ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2); + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2); const rct::key C = tx.rct_signatures.outPk[n].mask; rct::key Ctmp; THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); @@ -10497,13 +10681,13 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de check_tx_key_helper(tx, derivation, additional_derivations, address, received); in_pool = res.txs.front().in_pool; - confirmations = (uint64_t)-1; + confirmations = 0; if (!in_pool) { std::string err; uint64_t bc_height = get_daemon_blockchain_height(err); if (err.empty()) - confirmations = bc_height - (res.txs.front().block_height + 1); + confirmations = bc_height - res.txs.front().block_height; } } @@ -10554,6 +10738,8 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message) const { + hw::device &hwdev = m_account.get_device(); + rct::key aP; // determine if the address is found in the subaddress hash table (i.e. whether the proof is outbound or inbound) const bool is_out = m_subaddresses.count(address.m_spend_public_key) == 0; @@ -10572,30 +10758,34 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt shared_secret.resize(num_sigs); sig.resize(num_sigs); - shared_secret[0] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); + hwdev.scalarmultKey(aP, rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key)); + shared_secret[0] = rct::rct2pk(aP); crypto::public_key tx_pub_key; if (is_subaddress) { - tx_pub_key = rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key))); - crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, sig[0]); + hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key)); + tx_pub_key = rct2pk(aP); + hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, sig[0]); } else { - crypto::secret_key_to_public_key(tx_key, tx_pub_key); - crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]); + hwdev.secret_key_to_public_key(tx_key, tx_pub_key); + hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]); } for (size_t i = 1; i < num_sigs; ++i) { - shared_secret[i] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(additional_tx_keys[i - 1]))); + hwdev.scalarmultKey(aP, rct::pk2rct(address.m_view_public_key), rct::sk2rct(additional_tx_keys[i - 1])); + shared_secret[i] = rct::rct2pk(aP); if (is_subaddress) { - tx_pub_key = rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_spend_public_key), rct::sk2rct(additional_tx_keys[i - 1]))); - crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]); + hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(additional_tx_keys[i - 1])); + tx_pub_key = rct2pk(aP); + hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]); } else { - crypto::secret_key_to_public_key(additional_tx_keys[i - 1], tx_pub_key); - crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]); + hwdev.secret_key_to_public_key(additional_tx_keys[i - 1], tx_pub_key); + hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]); } } sig_str = std::string("OutProofV1"); @@ -10611,25 +10801,27 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt sig.resize(num_sigs); const crypto::secret_key& a = m_account.get_keys().m_view_secret_key; - shared_secret[0] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(tx_pub_key), rct::sk2rct(a))); + hwdev.scalarmultKey(aP, rct::pk2rct(tx_pub_key), rct::sk2rct(a)); + shared_secret[0] = rct2pk(aP); if (is_subaddress) { - crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], a, sig[0]); + hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], a, sig[0]); } else { - crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], a, sig[0]); + hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], a, sig[0]); } for (size_t i = 1; i < num_sigs; ++i) { - shared_secret[i] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(additional_tx_pub_keys[i - 1]), rct::sk2rct(a))); + hwdev.scalarmultKey(aP,rct::pk2rct(additional_tx_pub_keys[i - 1]), rct::sk2rct(a)); + shared_secret[i] = rct2pk(aP); if (is_subaddress) { - crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], address.m_spend_public_key, shared_secret[i], a, sig[i]); + hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], address.m_spend_public_key, shared_secret[i], a, sig[i]); } else { - crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]); + hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]); } } sig_str = std::string("InProofV1"); @@ -10691,13 +10883,13 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account return false; in_pool = res.txs.front().in_pool; - confirmations = (uint64_t)-1; + confirmations = 0; if (!in_pool) { std::string err; uint64_t bc_height = get_daemon_blockchain_height(err); if (err.empty()) - confirmations = bc_height - (res.txs.front().block_height + 1); + confirmations = bc_height - res.txs.front().block_height; } return true; @@ -10807,7 +10999,7 @@ std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t, for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details &td = m_transfers[i]; - if (!td.m_spent && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major)) + if (!td.m_spent && !td.m_frozen && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major)) selected_transfers.push_back(i); } @@ -11554,6 +11746,8 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag for(size_t i = 0; i < offset; ++i) { const transfer_details &td = m_transfers[i]; + if (td.m_frozen) + continue; uint64_t amount = td.amount(); if (td.m_spent) spent += amount; @@ -11565,6 +11759,8 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag for(size_t i = 0; i < signed_key_images.size(); ++i) { const transfer_details &td = m_transfers[i + offset]; + if (td.m_frozen) + continue; uint64_t amount = td.amount(); if (td.m_spent) spent += amount; @@ -12756,6 +12952,12 @@ void wallet2::on_device_button_request(uint64_t code) m_callback->on_device_button_request(code); } //---------------------------------------------------------------------------------------------------- +void wallet2::on_device_button_pressed() +{ + if (nullptr != m_callback) + m_callback->on_device_button_pressed(); +} +//---------------------------------------------------------------------------------------------------- boost::optional<epee::wipeable_string> wallet2::on_device_pin_request() { if (nullptr != m_callback) @@ -12854,4 +13056,14 @@ void wallet2::finish_rescan_bc_keep_key_images(uint64_t transfer_height, const c m_transfers[it->second].m_key_image_known = true; } } +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_bytes_sent() const +{ + return m_http_client.get_bytes_sent(); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_bytes_received() const +{ + return m_http_client.get_bytes_received(); +} } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 0d13235bd..a57f869c5 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -62,6 +62,7 @@ #include "common/password.h" #include "node_rpc_proxy.h" #include "message_store.h" +#include "wallet_light_rpc.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" @@ -104,6 +105,7 @@ namespace tools virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) {} // Device callbacks virtual void on_device_button_request(uint64_t code) {} + virtual void on_device_button_pressed() {} virtual boost::optional<epee::wipeable_string> on_device_pin_request() { return boost::none; } virtual boost::optional<epee::wipeable_string> on_device_passphrase_request(bool on_device) { return boost::none; } virtual void on_device_progress(const hw::device_progress& event) {}; @@ -117,6 +119,7 @@ namespace tools public: wallet_device_callback(wallet2 * wallet): wallet(wallet) {}; void on_button_request(uint64_t code=0) override; + void on_button_pressed() override; boost::optional<epee::wipeable_string> on_pin_request() override; boost::optional<epee::wipeable_string> on_passphrase_request(bool on_device) override; void on_progress(const hw::device_progress& event) override; @@ -193,6 +196,12 @@ namespace tools AskPasswordToDecrypt = 2, }; + enum BackgroundMiningSetupType { + BackgroundMiningMaybe = 0, + BackgroundMiningYes = 1, + BackgroundMiningNo = 2, + }; + static const char* tr(const char* str); static bool has_testnet_option(const boost::program_options::variables_map& vm); @@ -265,6 +274,7 @@ namespace tools size_t m_internal_output_index; uint64_t m_global_output_index; bool m_spent; + bool m_frozen; uint64_t m_spent_height; crypto::key_image m_key_image; //TODO: key_image stored twice :( rct::key m_mask; @@ -290,6 +300,7 @@ namespace tools FIELD(m_internal_output_index) FIELD(m_global_output_index) FIELD(m_spent) + FIELD(m_frozen) FIELD(m_spent_height) FIELD(m_key_image) FIELD(m_mask) @@ -530,6 +541,8 @@ namespace tools std::vector<cryptonote::tx_extra_field> tx_extra_fields; std::vector<is_out_data> primary; std::vector<is_out_data> additional; + + bool empty() const { return tx_extra_fields.empty() && primary.empty() && additional.empty(); } }; /*! @@ -684,10 +697,10 @@ namespace tools boost::asio::ip::tcp::endpoint proxy = {}, uint64_t upper_transaction_weight_limit = 0, bool trusted_daemon = true, - epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, - const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, - const std::list<std::string> &allowed_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_fingerprints = {}, - bool allow_any_cert = false); + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect); + bool set_daemon(std::string daemon_address = "http://localhost:8080", + boost::optional<epee::net_utils::http::login> daemon_login = boost::none, bool trusted_daemon = true, + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect); void stop() { m_run.store(false, std::memory_order_relaxed); m_message_store.stop(); } @@ -762,13 +775,13 @@ namespace tools // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major) const; - uint64_t unlocked_balance(uint32_t subaddr_index_major) const; + uint64_t unlocked_balance(uint32_t subaddr_index_major, uint64_t *blocks_to_unlock = NULL) const; // locked & unlocked balance per subaddress of given or current subaddress account std::map<uint32_t, uint64_t> balance_per_subaddress(uint32_t subaddr_index_major) const; - std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress(uint32_t subaddr_index_major) const; + std::map<uint32_t, std::pair<uint64_t, uint64_t>> unlocked_balance_per_subaddress(uint32_t subaddr_index_major) const; // all locked & unlocked balances of all subaddress accounts uint64_t balance_all() const; - uint64_t unlocked_balance_all() const; + uint64_t unlocked_balance_all(uint64_t *blocks_to_unlock = NULL) const; template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, @@ -1005,6 +1018,8 @@ namespace tools void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; } bool track_uses() const { return m_track_uses; } void track_uses(bool value) { m_track_uses = value; } + BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; } + void setup_background_mining(BackgroundMiningSetupType value) { m_setup_background_mining = value; } const std::string & device_name() const { return m_device_name; } void device_name(const std::string & device_name) { m_device_name = device_name; } const std::string & device_derivation_path() const { return m_device_derivation_path; } @@ -1178,11 +1193,11 @@ namespace tools // fetch txs and store in m_payments void light_wallet_get_address_txs(); // get_address_info - bool light_wallet_get_address_info(cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response &response); + bool light_wallet_get_address_info(tools::COMMAND_RPC_GET_ADDRESS_INFO::response &response); // Login. new_address is true if address hasn't been used on lw node before. bool light_wallet_login(bool &new_address); // Send an import request to lw node. returns info about import fee, address and payment_id - bool light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response); + bool light_wallet_import_wallet_request(tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response); // get random outputs from light wallet server void light_wallet_get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count); // Parse rct string @@ -1238,6 +1253,8 @@ namespace tools bool get_ring(const crypto::key_image &key_image, std::vector<uint64_t> &outs); bool get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &outs); bool set_ring(const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); + bool unset_ring(const std::vector<crypto::key_image> &key_images); + bool unset_ring(const crypto::hash &txid); bool find_and_save_rings(bool force = true); bool blackball_output(const std::pair<uint64_t, uint64_t> &output); @@ -1245,6 +1262,17 @@ namespace tools bool unblackball_output(const std::pair<uint64_t, uint64_t> &output); bool is_output_blackballed(const std::pair<uint64_t, uint64_t> &output) const; + void freeze(size_t idx); + void thaw(size_t idx); + bool frozen(size_t idx) const; + void freeze(const crypto::key_image &ki); + void thaw(const crypto::key_image &ki); + bool frozen(const crypto::key_image &ki) const; + bool frozen(const transfer_details &td) const; + + uint64_t get_bytes_sent() const; + uint64_t get_bytes_received() const; + // MMS ------------------------------------------------------------------------------------------------- mms::message_store& get_message_store() { return m_message_store; }; const mms::message_store& get_message_store() const { return m_message_store; }; @@ -1279,8 +1307,9 @@ namespace tools */ bool load_keys(const std::string& keys_file_name, const epee::wipeable_string& password); void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); + bool should_skip_block(const cryptonote::block &b, uint64_t height) const; void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); - void detach_blockchain(uint64_t height); + void detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const; bool clear(); void clear_soft(bool keep_key_images=false); @@ -1327,6 +1356,7 @@ namespace tools bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); crypto::chacha_key get_ringdb_key(); void setup_keys(const epee::wipeable_string &password); + size_t get_transfer_details(const crypto::key_image &ki) const; void register_devices(); hw::device& lookup_device(const std::string & device_descriptor); @@ -1344,11 +1374,13 @@ namespace tools void cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const; std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> create_output_tracker_cache() const; + void init_type(hw::device::device_type device_type); void setup_new_blockchain(); void create_keys_file(const std::string &wallet_, bool watch_only, const epee::wipeable_string &password, bool create_address_file); wallet_device_callback * get_device_callback(); void on_device_button_request(uint64_t code); + void on_device_button_pressed(); boost::optional<epee::wipeable_string> on_device_pin_request(); boost::optional<epee::wipeable_string> on_device_passphrase_request(bool on_device); void on_device_progress(const hw::device_progress& event); @@ -1432,6 +1464,7 @@ namespace tools uint64_t m_segregation_height; bool m_ignore_fractional_outputs; bool m_track_uses; + BackgroundMiningSetupType m_setup_background_mining; bool m_is_initialized; NodeRPCProxy m_node_rpc_proxy; std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; @@ -1481,7 +1514,7 @@ namespace tools }; } BOOST_CLASS_VERSION(tools::wallet2, 28) -BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 11) +BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1) @@ -1543,6 +1576,10 @@ namespace boost { x.m_key_image_request = false; } + if (ver < 12) + { + x.m_frozen = false; + } } template <class Archive> @@ -1631,8 +1668,17 @@ namespace boost } a & x.m_key_image_request; if (ver < 11) + { + initialize_transfer_details(a, x, ver); return; + } a & x.m_uses; + if (ver < 12) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_frozen; } template <class Archive> diff --git a/src/wallet/wallet_light_rpc.h b/src/wallet/wallet_light_rpc.h new file mode 100644 index 000000000..1d35cec33 --- /dev/null +++ b/src/wallet/wallet_light_rpc.h @@ -0,0 +1,320 @@ +// Copyright (c) 2014-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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include "cryptonote_basic/cryptonote_basic.h" +#include "crypto/hash.h" + +namespace tools +{ + //----------------------------------------------- + struct COMMAND_RPC_GET_ADDRESS_TXS + { + struct request_t + { + std::string address; + std::string view_key; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(view_key) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct spent_output { + uint64_t amount; + std::string key_image; + std::string tx_pub_key; + uint64_t out_index; + uint32_t mixin; + + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) + KV_SERIALIZE(key_image) + KV_SERIALIZE(tx_pub_key) + KV_SERIALIZE(out_index) + KV_SERIALIZE(mixin) + END_KV_SERIALIZE_MAP() + }; + + struct transaction + { + uint64_t id; + std::string hash; + uint64_t timestamp; + uint64_t total_received; + uint64_t total_sent; + uint64_t unlock_time; + uint64_t height; + std::list<spent_output> spent_outputs; + std::string payment_id; + bool coinbase; + bool mempool; + uint32_t mixin; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(id) + KV_SERIALIZE(hash) + KV_SERIALIZE(timestamp) + KV_SERIALIZE(total_received) + KV_SERIALIZE(total_sent) + KV_SERIALIZE(unlock_time) + KV_SERIALIZE(height) + KV_SERIALIZE(spent_outputs) + KV_SERIALIZE(payment_id) + KV_SERIALIZE(coinbase) + KV_SERIALIZE(mempool) + KV_SERIALIZE(mixin) + END_KV_SERIALIZE_MAP() + }; + + + struct response_t + { + //std::list<std::string> txs_as_json; + uint64_t total_received; + uint64_t total_received_unlocked = 0; // OpenMonero only + uint64_t scanned_height; + std::vector<transaction> transactions; + uint64_t blockchain_height; + uint64_t scanned_block_height; + std::string status; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(total_received) + KV_SERIALIZE(total_received_unlocked) + KV_SERIALIZE(scanned_height) + KV_SERIALIZE(transactions) + KV_SERIALIZE(blockchain_height) + KV_SERIALIZE(scanned_block_height) + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + //----------------------------------------------- + struct COMMAND_RPC_GET_ADDRESS_INFO + { + struct request_t + { + std::string address; + std::string view_key; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(view_key) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct spent_output + { + uint64_t amount; + std::string key_image; + std::string tx_pub_key; + uint64_t out_index; + uint32_t mixin; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) + KV_SERIALIZE(key_image) + KV_SERIALIZE(tx_pub_key) + KV_SERIALIZE(out_index) + KV_SERIALIZE(mixin) + END_KV_SERIALIZE_MAP() + }; + + struct response_t + { + uint64_t locked_funds; + uint64_t total_received; + uint64_t total_sent; + uint64_t scanned_height; + uint64_t scanned_block_height; + uint64_t start_height; + uint64_t transaction_height; + uint64_t blockchain_height; + std::list<spent_output> spent_outputs; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(locked_funds) + KV_SERIALIZE(total_received) + KV_SERIALIZE(total_sent) + KV_SERIALIZE(scanned_height) + KV_SERIALIZE(scanned_block_height) + KV_SERIALIZE(start_height) + KV_SERIALIZE(transaction_height) + KV_SERIALIZE(blockchain_height) + KV_SERIALIZE(spent_outputs) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + //----------------------------------------------- + struct COMMAND_RPC_GET_UNSPENT_OUTS + { + struct request_t + { + std::string amount; + std::string address; + std::string view_key; + // OpenMonero specific + uint64_t mixin; + bool use_dust; + std::string dust_threshold; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) + KV_SERIALIZE(address) + KV_SERIALIZE(view_key) + KV_SERIALIZE(mixin) + KV_SERIALIZE(use_dust) + KV_SERIALIZE(dust_threshold) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + + struct output { + uint64_t amount; + std::string public_key; + uint64_t index; + uint64_t global_index; + std::string rct; + std::string tx_hash; + std::string tx_pub_key; + std::string tx_prefix_hash; + std::vector<std::string> spend_key_images; + uint64_t timestamp; + uint64_t height; + + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) + KV_SERIALIZE(public_key) + KV_SERIALIZE(index) + KV_SERIALIZE(global_index) + KV_SERIALIZE(rct) + KV_SERIALIZE(tx_hash) + KV_SERIALIZE(tx_pub_key) + KV_SERIALIZE(tx_prefix_hash) + KV_SERIALIZE(spend_key_images) + KV_SERIALIZE(timestamp) + KV_SERIALIZE(height) + END_KV_SERIALIZE_MAP() + }; + + struct response_t + { + uint64_t amount; + std::list<output> outputs; + uint64_t per_kb_fee; + std::string status; + std::string reason; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) + KV_SERIALIZE(outputs) + KV_SERIALIZE(per_kb_fee) + KV_SERIALIZE(status) + KV_SERIALIZE(reason) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + //----------------------------------------------- + struct COMMAND_RPC_LOGIN + { + struct request_t + { + std::string address; + std::string view_key; + bool create_account; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(view_key) + KV_SERIALIZE(create_account) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + std::string status; + std::string reason; + bool new_address; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(reason) + KV_SERIALIZE(new_address) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + //----------------------------------------------- + struct COMMAND_RPC_IMPORT_WALLET_REQUEST + { + struct request_t + { + std::string address; + std::string view_key; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(view_key) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + std::string payment_id; + uint64_t import_fee; + bool new_request; + bool request_fulfilled; + std::string payment_address; + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(payment_id) + KV_SERIALIZE(import_fee) + KV_SERIALIZE(new_request) + KV_SERIALIZE(request_fulfilled) + KV_SERIALIZE(payment_address) + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + //----------------------------------------------- +} diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index a1f60ea01..71c64d3c1 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -56,6 +56,8 @@ using namespace epee; #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" +#define DEFAULT_AUTO_REFRESH_PERIOD 20 // seconds + namespace { const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"}; @@ -66,7 +68,7 @@ namespace const command_line::arg_descriptor<std::string> arg_rpc_ssl = {"rpc-ssl", tools::wallet2::tr("Enable SSL on wallet RPC connections: enabled|disabled|autodetect"), "autodetect"}; const command_line::arg_descriptor<std::string> arg_rpc_ssl_private_key = {"rpc-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""}; const command_line::arg_descriptor<std::string> arg_rpc_ssl_certificate = {"rpc-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""}; - const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_certificates = {"rpc-ssl-allowed-certificates", tools::wallet2::tr("List of paths to PEM format certificates of allowed RPC servers (all allowed if empty)")}; + const command_line::arg_descriptor<std::string> arg_rpc_ssl_ca_certificates = {"rpc-ssl-ca-certificates", tools::wallet2::tr("Path to file containing concatenated PEM format certificate(s) to replace system CA(s).")}; const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_fingerprints = {"rpc-ssl-allowed-fingerprints", tools::wallet2::tr("List of certificate fingerprints to allow")}; constexpr const char default_rpc_username[] = "monero"; @@ -83,7 +85,7 @@ namespace //------------------------------------------------------------------------------------------------------------------------------ void set_confirmations(tools::wallet_rpc::transfer_entry &entry, uint64_t blockchain_height, uint64_t block_reward) { - if (entry.height >= blockchain_height) + if (entry.height >= blockchain_height || (entry.height == 0 && (!strcmp(entry.type.c_str(), "pending") || !strcmp(entry.type.c_str(), "pool")))) { entry.confirmations = 0; entry.suggested_confirmations_threshold = 0; @@ -124,13 +126,18 @@ namespace tools { m_stop = false; m_net_server.add_idle_handler([this](){ + if (m_auto_refresh_period == 0) // disabled + return true; + if (boost::posix_time::microsec_clock::universal_time() < m_last_auto_refresh_time + boost::posix_time::seconds(m_auto_refresh_period)) + return true; try { if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon()); } catch (const std::exception& ex) { LOG_ERROR("Exception at while refreshing, what=" << ex.what()); } + m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time(); return true; - }, 20000); + }, 1000); m_net_server.add_idle_handler([this](){ if (m_stop.load(std::memory_order_relaxed)) { @@ -240,37 +247,102 @@ namespace tools auto rpc_ssl_private_key = command_line::get_arg(vm, arg_rpc_ssl_private_key); auto rpc_ssl_certificate = command_line::get_arg(vm, arg_rpc_ssl_certificate); - auto rpc_ssl_allowed_certificates = command_line::get_arg(vm, arg_rpc_ssl_allowed_certificates); + auto rpc_ssl_ca_file = command_line::get_arg(vm, arg_rpc_ssl_ca_certificates); auto rpc_ssl_allowed_fingerprints = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints); auto rpc_ssl = command_line::get_arg(vm, arg_rpc_ssl); - epee::net_utils::ssl_support_t rpc_ssl_support; - if (!epee::net_utils::ssl_support_from_string(rpc_ssl_support, rpc_ssl)) + epee::net_utils::ssl_options_t rpc_ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + + if (!rpc_ssl_ca_file.empty() || !rpc_ssl_allowed_fingerprints.empty()) { - MERROR("Invalid argument for " << std::string(arg_rpc_ssl.name)); - return false; + std::vector<std::vector<uint8_t>> allowed_fingerprints{ rpc_ssl_allowed_fingerprints.size() }; + std::transform(rpc_ssl_allowed_fingerprints.begin(), rpc_ssl_allowed_fingerprints.end(), allowed_fingerprints.begin(), epee::from_hex::vector); + + rpc_ssl_options = epee::net_utils::ssl_options_t{ + std::move(allowed_fingerprints), std::move(rpc_ssl_ca_file) + }; } - std::list<std::string> allowed_certificates; - for (const std::string &path: rpc_ssl_allowed_certificates) + + // user specified CA file or fingeprints implies enabled SSL by default + if (rpc_ssl_options.verification != epee::net_utils::ssl_verification_t::user_certificates || !command_line::is_arg_defaulted(vm, arg_rpc_ssl)) { - allowed_certificates.push_back({}); - if (!epee::file_io_utils::load_file_to_string(path, allowed_certificates.back())) - { - MERROR("Failed to load certificate: " << path); - allowed_certificates.back() = std::string(); - } + if (!epee::net_utils::ssl_support_from_string(rpc_ssl_options.support, rpc_ssl)) + { + MERROR("Invalid argument for " << std::string(arg_rpc_ssl.name)); + return false; + } } - std::vector<std::vector<uint8_t>> allowed_fingerprints{ rpc_ssl_allowed_fingerprints.size() }; - std::transform(rpc_ssl_allowed_fingerprints.begin(), rpc_ssl_allowed_fingerprints.end(), allowed_fingerprints.begin(), epee::from_hex::vector); + rpc_ssl_options.auth = epee::net_utils::ssl_authentication_t{ + std::move(rpc_ssl_private_key), std::move(rpc_ssl_certificate) + }; + + m_auto_refresh_period = DEFAULT_AUTO_REFRESH_PERIOD; + m_last_auto_refresh_time = boost::posix_time::min_date_time; + + check_background_mining(); m_net_server.set_threads_prefix("RPC"); auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); }; return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init( rng, std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), - rpc_ssl_support, std::make_pair(rpc_ssl_private_key, rpc_ssl_certificate), std::move(allowed_certificates), std::move(allowed_fingerprints) + std::move(rpc_ssl_options) ); } //------------------------------------------------------------------------------------------------------------------------------ + void wallet_rpc_server::check_background_mining() + { + if (!m_wallet) + return; + + tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining(); + if (setup == tools::wallet2::BackgroundMiningNo) + { + MLOG_RED(el::Level::Warning, "Background mining not enabled. Run \"set setup-background-mining 1\" in monero-wallet-cli to change."); + return; + } + + if (!m_wallet->is_trusted_daemon()) + { + MDEBUG("Using an untrusted daemon, skipping background mining check"); + return; + } + + cryptonote::COMMAND_RPC_MINING_STATUS::request req; + cryptonote::COMMAND_RPC_MINING_STATUS::response res; + bool r = m_wallet->invoke_http_json("/mining_status", req, res); + if (!r || res.status != CORE_RPC_STATUS_OK) + { + MERROR("Failed to query mining status: " << (r ? res.status : "No connection to daemon")); + return; + } + if (res.active || res.is_background_mining_enabled) + return; + + if (setup == tools::wallet2::BackgroundMiningMaybe) + { + MINFO("The daemon is not set up to background mine."); + MINFO("With background mining enabled, the daemon will mine when idle and not on batttery."); + MINFO("Enabling this supports the network you are using, and makes you eligible for receiving new monero"); + MINFO("Set setup-background-mining to 1 in monero-wallet-cli to change."); + return; + } + + cryptonote::COMMAND_RPC_START_MINING::request req2; + cryptonote::COMMAND_RPC_START_MINING::response res2; + req2.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); + req2.threads_count = 1; + req2.do_background_mining = true; + req2.ignore_battery = false; + r = m_wallet->invoke_http_json("/start_mining", req2, res); + if (!r || res2.status != CORE_RPC_STATUS_OK) + { + MERROR("Failed to setup background mining: " << (r ? res.status : "No connection to daemon")); + return; + } + + MINFO("Background mining enabled. The daemon will mine when idle and not on batttery."); + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::not_open(epee::json_rpc::error& er) { er.code = WALLET_RPC_ERROR_CODE_NOT_OPEN; @@ -292,6 +364,7 @@ namespace tools entry.note = m_wallet->get_tx_note(pd.m_tx_hash); entry.type = pd.m_coinbase ? "block" : "in"; entry.subaddr_index = pd.m_subaddr_index; + entry.subaddr_indices.push_back(pd.m_subaddr_index); entry.address = m_wallet->get_subaddress_as_str(pd.m_subaddr_index); set_confirmations(entry, m_wallet->get_blockchain_current_height(), m_wallet->get_last_block_reward()); } @@ -363,6 +436,7 @@ namespace tools entry.double_spend_seen = ppd.m_double_spend_seen; entry.type = "pool"; entry.subaddr_index = pd.m_subaddr_index; + entry.subaddr_indices.push_back(pd.m_subaddr_index); entry.address = m_wallet->get_subaddress_as_str(pd.m_subaddr_index); set_confirmations(entry, m_wallet->get_blockchain_current_height(), m_wallet->get_last_block_reward()); } @@ -373,10 +447,10 @@ namespace tools try { res.balance = req.all_accounts ? m_wallet->balance_all() : m_wallet->balance(req.account_index); - res.unlocked_balance = req.all_accounts ? m_wallet->unlocked_balance_all() : m_wallet->unlocked_balance(req.account_index); + res.unlocked_balance = req.all_accounts ? m_wallet->unlocked_balance_all(&res.blocks_to_unlock) : m_wallet->unlocked_balance(req.account_index, &res.blocks_to_unlock); res.multisig_import_needed = m_wallet->multisig() && m_wallet->has_multisig_partial_key_images(); std::map<uint32_t, std::map<uint32_t, uint64_t>> balance_per_subaddress_per_account; - std::map<uint32_t, std::map<uint32_t, uint64_t>> unlocked_balance_per_subaddress_per_account; + std::map<uint32_t, std::map<uint32_t, std::pair<uint64_t, uint64_t>>> unlocked_balance_per_subaddress_per_account; if (req.all_accounts) { for (uint32_t account_index = 0; account_index < m_wallet->get_num_subaddress_accounts(); ++account_index) @@ -396,7 +470,7 @@ namespace tools { uint32_t account_index = p.first; std::map<uint32_t, uint64_t> balance_per_subaddress = p.second; - std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = unlocked_balance_per_subaddress_per_account[account_index]; + std::map<uint32_t, std::pair<uint64_t, uint64_t>> unlocked_balance_per_subaddress = unlocked_balance_per_subaddress_per_account[account_index]; std::set<uint32_t> address_indices; if (!req.all_accounts && !req.address_indices.empty()) { @@ -415,7 +489,8 @@ namespace tools cryptonote::subaddress_index index = {info.account_index, info.address_index}; info.address = m_wallet->get_subaddress_as_str(index); info.balance = balance_per_subaddress[i]; - info.unlocked_balance = unlocked_balance_per_subaddress[i]; + info.unlocked_balance = unlocked_balance_per_subaddress[i].first; + info.blocks_to_unlock = unlocked_balance_per_subaddress[i].second; info.label = m_wallet->get_subaddress_label(index); info.num_unspent_outputs = std::count_if(transfers.begin(), transfers.end(), [&](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == index; }); res.per_subaddress.emplace_back(std::move(info)); @@ -891,15 +966,7 @@ namespace tools try { - uint64_t mixin; - if(req.ring_size != 0) - { - mixin = m_wallet->adjust_mixin(req.ring_size - 1); - } - else - { - mixin = m_wallet->adjust_mixin(req.mixin); - } + uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); uint32_t priority = m_wallet->adjust_priority(req.priority); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); @@ -951,15 +1018,7 @@ namespace tools try { - uint64_t mixin; - if(req.ring_size != 0) - { - mixin = m_wallet->adjust_mixin(req.ring_size - 1); - } - else - { - mixin = m_wallet->adjust_mixin(req.mixin); - } + uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); uint32_t priority = m_wallet->adjust_priority(req.priority); LOG_PRINT_L2("on_transfer_split calling create_transactions_2"); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); @@ -1177,7 +1236,7 @@ namespace tools { const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d]; std::string address = cryptonote::get_account_address_as_str(m_wallet->nettype(), entry.is_subaddress, entry.addr); - if (has_encrypted_payment_id && !entry.is_subaddress) + if (has_encrypted_payment_id && !entry.is_subaddress && address != entry.original) address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), entry.addr, payment_id8); auto i = dests.find(entry.addr); if (i == dests.end()) @@ -1369,15 +1428,7 @@ namespace tools try { - uint64_t mixin; - if(req.ring_size != 0) - { - mixin = m_wallet->adjust_mixin(req.ring_size - 1); - } - else - { - mixin = m_wallet->adjust_mixin(req.mixin); - } + uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); 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, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); @@ -1432,15 +1483,7 @@ namespace tools try { - uint64_t mixin; - if(req.ring_size != 0) - { - mixin = m_wallet->adjust_mixin(req.ring_size - 1); - } - else - { - mixin = m_wallet->adjust_mixin(req.mixin); - } + uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); 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, req.outputs, mixin, req.unlock_time, priority, extra); @@ -1852,7 +1895,7 @@ namespace tools if (m_wallet->watch_only()) { er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; - er.message = "The wallet is watch-only. Cannot display seed."; + er.message = "The wallet is watch-only. Cannot retrieve seed."; return false; } if (!m_wallet->is_deterministic()) @@ -1877,6 +1920,12 @@ namespace tools } else if(req.key_type.compare("spend_key") == 0) { + if (m_wallet->watch_only()) + { + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; + er.message = "The wallet is watch-only. Cannot retrieve spend key."; + return false; + } epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().m_spend_secret_key); res.key = std::string(key.data(), key.size()); } @@ -2834,6 +2883,28 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_auto_refresh(const wallet_rpc::COMMAND_RPC_AUTO_REFRESH::request& req, wallet_rpc::COMMAND_RPC_AUTO_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (m_restricted) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + try + { + m_auto_refresh_period = req.enable ? req.period ? req.period : DEFAULT_AUTO_REFRESH_PERIOD : 0; + MINFO("Auto refresh now " << (m_auto_refresh_period ? std::to_string(m_auto_refresh_period) + " seconds" : std::string("disabled"))); + return true; + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); @@ -2908,7 +2979,8 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx) { - crypto::ElectrumWords::get_language_list(res.languages); + crypto::ElectrumWords::get_language_list(res.languages, true); + crypto::ElectrumWords::get_language_list(res.languages_local, false); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2936,17 +3008,22 @@ namespace tools er.message = "Invalid filename"; return false; } - std::string wallet_file = m_wallet_dir + "/" + req.filename; + std::string wallet_file = req.filename.empty() ? "" : (m_wallet_dir + "/" + req.filename); { std::vector<std::string> languages; - crypto::ElectrumWords::get_language_list(languages); + crypto::ElectrumWords::get_language_list(languages, false); std::vector<std::string>::iterator it; it = std::find(languages.begin(), languages.end(), req.language); if (it == languages.end()) { + crypto::ElectrumWords::get_language_list(languages, true); + it = std::find(languages.begin(), languages.end(), req.language); + } + if (it == languages.end()) + { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Unknown language"; + er.message = "Unknown language: " + req.language; return false; } } @@ -3221,12 +3298,6 @@ namespace tools } // early check for mandatory fields - if (req.filename.empty()) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "field 'filename' is mandatory. Please provide a filename to save the restored wallet to."; - return false; - } if (req.viewkey.empty()) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -3255,7 +3326,7 @@ namespace tools er.message = "Invalid filename"; return false; } - std::string wallet_file = m_wallet_dir + "/" + req.filename; + std::string wallet_file = req.filename.empty() ? "" : (m_wallet_dir + "/" + req.filename); // check if wallet file already exists if (!wallet_file.empty()) { @@ -3365,7 +3436,8 @@ namespace tools { try { - m_wallet->store(); + if (!wallet_file.empty()) + m_wallet->store(); } catch (const std::exception &e) { @@ -3389,12 +3461,6 @@ namespace tools } // early check for mandatory fields - if (req.filename.empty()) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "field 'filename' is mandatory. Please provide a filename to save the restored wallet to."; - return false; - } if (req.seed.empty()) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -3417,7 +3483,7 @@ namespace tools er.message = "Invalid filename"; return false; } - std::string wallet_file = m_wallet_dir + "/" + req.filename; + std::string wallet_file = req.filename.empty() ? "" : (m_wallet_dir + "/" + req.filename); // check if wallet file already exists if (!wallet_file.empty()) { @@ -3533,7 +3599,7 @@ namespace tools er.message = "Failed to encode seed"; return false; } - res.seed = electrum_words.data(); + res.seed = std::string(electrum_words.data(), electrum_words.size()); if (!wal) { @@ -4042,6 +4108,58 @@ namespace tools return false; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_set_daemon(const wallet_rpc::COMMAND_RPC_SET_DAEMON::request& req, wallet_rpc::COMMAND_RPC_SET_DAEMON::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + 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; + } + + std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints; + ssl_allowed_fingerprints.reserve(req.ssl_allowed_fingerprints.size()); + for (const std::string &fp: req.ssl_allowed_fingerprints) + { + ssl_allowed_fingerprints.push_back({}); + std::vector<uint8_t> &v = ssl_allowed_fingerprints.back(); + for (auto c: fp) + v.push_back(c); + } + + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + if (req.ssl_allow_any_cert) + ssl_options.verification = epee::net_utils::ssl_verification_t::none; + else if (!ssl_allowed_fingerprints.empty() || !req.ssl_ca_file.empty()) + ssl_options = epee::net_utils::ssl_options_t{std::move(ssl_allowed_fingerprints), std::move(req.ssl_ca_file)}; + + if (!epee::net_utils::ssl_support_from_string(ssl_options.support, req.ssl_support)) + { + er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; + er.message = std::string("Invalid ssl support mode"); + return false; + } + + ssl_options.auth = epee::net_utils::ssl_authentication_t{ + std::move(req.ssl_private_key_path), std::move(req.ssl_certificate_path) + }; + + if (ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_enabled && !ssl_options.has_strong_verification(boost::string_ref{})) + { + er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; + er.message = "SSL is enabled but no user certificate or fingerprints were provided"; + } + + if (!m_wallet->set_daemon(req.address, boost::none, req.trusted, std::move(ssl_options))) + { + er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; + er.message = std::string("Unable to set daemon"); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx) { res.version = WALLET_RPC_VERSION; @@ -4241,7 +4359,7 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_rpc_ssl); command_line::add_arg(desc_params, arg_rpc_ssl_private_key); command_line::add_arg(desc_params, arg_rpc_ssl_certificate); - command_line::add_arg(desc_params, arg_rpc_ssl_allowed_certificates); + command_line::add_arg(desc_params, arg_rpc_ssl_ca_certificates); command_line::add_arg(desc_params, arg_rpc_ssl_allowed_fingerprints); daemonizer::init_options(hidden_options, desc_params); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index affaf10f7..7d2272dd0 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -129,6 +129,7 @@ namespace tools MAP_JON_RPC_WE("add_address_book", on_add_address_book, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY) MAP_JON_RPC_WE("delete_address_book",on_delete_address_book,wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY) MAP_JON_RPC_WE("refresh", on_refresh, wallet_rpc::COMMAND_RPC_REFRESH) + MAP_JON_RPC_WE("auto_refresh", on_auto_refresh, wallet_rpc::COMMAND_RPC_AUTO_REFRESH) MAP_JON_RPC_WE("rescan_spent", on_rescan_spent, wallet_rpc::COMMAND_RPC_RESCAN_SPENT) MAP_JON_RPC_WE("start_mining", on_start_mining, wallet_rpc::COMMAND_RPC_START_MINING) MAP_JON_RPC_WE("stop_mining", on_stop_mining, wallet_rpc::COMMAND_RPC_STOP_MINING) @@ -149,6 +150,7 @@ namespace tools 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("validate_address", on_validate_address, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS) + MAP_JON_RPC_WE("set_daemon", on_set_daemon, wallet_rpc::COMMAND_RPC_SET_DAEMON) MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION) END_JSON_RPC_MAP() END_URI_MAP2() @@ -210,6 +212,7 @@ namespace tools bool on_add_address_book(const wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_delete_address_book(const wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_refresh(const wallet_rpc::COMMAND_RPC_REFRESH::request& req, wallet_rpc::COMMAND_RPC_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_auto_refresh(const wallet_rpc::COMMAND_RPC_AUTO_REFRESH::request& req, wallet_rpc::COMMAND_RPC_AUTO_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_start_mining(const wallet_rpc::COMMAND_RPC_START_MINING::request& req, wallet_rpc::COMMAND_RPC_START_MINING::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_stop_mining(const wallet_rpc::COMMAND_RPC_STOP_MINING::request& req, wallet_rpc::COMMAND_RPC_STOP_MINING::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); @@ -230,6 +233,7 @@ namespace tools 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, const connection_context *ctx = NULL); 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, const connection_context *ctx = NULL); bool on_validate_address(const wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_set_daemon(const wallet_rpc::COMMAND_RPC_SET_DAEMON::request& req, wallet_rpc::COMMAND_RPC_SET_DAEMON::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); 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, const connection_context *ctx = NULL); //json rpc v2 @@ -250,11 +254,15 @@ namespace tools bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er); + void check_background_mining(); + wallet2 *m_wallet; std::string m_wallet_dir; tools::private_file rpc_login_file; std::atomic<bool> m_stop; bool m_restricted; const boost::program_options::variables_map *m_vm; + uint32_t m_auto_refresh_period; + boost::posix_time::ptime m_last_auto_refresh_time; }; } diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 36775fa1e..bb360ae01 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 8 +#define WALLET_RPC_VERSION_MINOR 9 #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 @@ -81,6 +81,7 @@ namespace wallet_rpc uint64_t unlocked_balance; std::string label; uint64_t num_unspent_outputs; + uint64_t blocks_to_unlock; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(account_index) @@ -90,6 +91,7 @@ namespace wallet_rpc KV_SERIALIZE(unlocked_balance) KV_SERIALIZE(label) KV_SERIALIZE(num_unspent_outputs) + KV_SERIALIZE(blocks_to_unlock) END_KV_SERIALIZE_MAP() }; @@ -99,12 +101,14 @@ namespace wallet_rpc uint64_t unlocked_balance; bool multisig_import_needed; std::vector<per_subaddress_info> per_subaddress; + uint64_t blocks_to_unlock; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(balance) KV_SERIALIZE(unlocked_balance) KV_SERIALIZE(multisig_import_needed) KV_SERIALIZE(per_subaddress) + KV_SERIALIZE(blocks_to_unlock) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -446,7 +450,6 @@ namespace wallet_rpc uint32_t account_index; std::set<uint32_t> subaddr_indices; uint32_t priority; - uint64_t mixin; uint64_t ring_size; uint64_t unlock_time; std::string payment_id; @@ -460,7 +463,6 @@ namespace wallet_rpc KV_SERIALIZE(account_index) KV_SERIALIZE(subaddr_indices) KV_SERIALIZE(priority) - KV_SERIALIZE_OPT(mixin, (uint64_t)0) KV_SERIALIZE_OPT(ring_size, (uint64_t)0) KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) @@ -505,7 +507,6 @@ namespace wallet_rpc uint32_t account_index; std::set<uint32_t> subaddr_indices; uint32_t priority; - uint64_t mixin; uint64_t ring_size; uint64_t unlock_time; std::string payment_id; @@ -519,7 +520,6 @@ namespace wallet_rpc KV_SERIALIZE(account_index) KV_SERIALIZE(subaddr_indices) KV_SERIALIZE(priority) - KV_SERIALIZE_OPT(mixin, (uint64_t)0) KV_SERIALIZE_OPT(ring_size, (uint64_t)0) KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) @@ -746,7 +746,6 @@ namespace wallet_rpc uint32_t account_index; std::set<uint32_t> subaddr_indices; uint32_t priority; - uint64_t mixin; uint64_t ring_size; uint64_t outputs; uint64_t unlock_time; @@ -762,7 +761,6 @@ namespace wallet_rpc KV_SERIALIZE(account_index) KV_SERIALIZE(subaddr_indices) 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) @@ -816,7 +814,6 @@ namespace wallet_rpc { std::string address; uint32_t priority; - uint64_t mixin; uint64_t ring_size; uint64_t outputs; uint64_t unlock_time; @@ -830,7 +827,6 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) 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) @@ -1932,6 +1928,28 @@ namespace wallet_rpc typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_AUTO_REFRESH + { + struct request_t + { + bool enable; + uint32_t period; // seconds + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_OPT(enable, true) + KV_SERIALIZE_OPT(period, (uint32_t)0) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + struct COMMAND_RPC_START_MINING { struct request_t @@ -1985,9 +2003,11 @@ namespace wallet_rpc struct response_t { std::vector<std::string> languages; + std::vector<std::string> languages_local; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(languages) + KV_SERIALIZE(languages_local) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2423,5 +2443,39 @@ namespace wallet_rpc typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_SET_DAEMON + { + struct request_t + { + std::string address; + bool trusted; + std::string ssl_support; // disabled, enabled, autodetect + std::string ssl_private_key_path; + std::string ssl_certificate_path; + std::string ssl_ca_file; + std::vector<std::string> ssl_allowed_fingerprints; + bool ssl_allow_any_cert; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE_OPT(trusted, false) + KV_SERIALIZE_OPT(ssl_support, (std::string)"autodetect") + KV_SERIALIZE(ssl_private_key_path) + KV_SERIALIZE(ssl_certificate_path) + KV_SERIALIZE(ssl_ca_file) + KV_SERIALIZE(ssl_allowed_fingerprints) + KV_SERIALIZE_OPT(ssl_allow_any_cert, false) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + } } |