diff options
Diffstat (limited to 'src')
50 files changed, 1906 insertions, 537 deletions
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 65b7f39d5..fe31321f3 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1178,6 +1178,9 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags) throw DB_ERROR("Database could not be opened"); } + if (tools::is_hdd(filename.c_str())) + MCLOG_RED(el::Level::Warning, "global", "The blockchain is on a rotating drive: this will be very slow, use a SSD if possible"); + m_folder = filename; #ifdef __OpenBSD__ @@ -1397,6 +1400,7 @@ void BlockchainLMDB::sync() void BlockchainLMDB::safesyncmode(const bool onoff) { + MINFO("switching safe mode " << (onoff ? "on" : "off")); mdb_env_set_flags(m_env, MDB_NOSYNC|MDB_MAPASYNC, !onoff); } diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index a5dd69556..338ec3e4b 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -100,7 +100,6 @@ target_link_libraries(blockchain_import PRIVATE cryptonote_core blockchain_db - p2p version epee ${Boost_FILESYSTEM_LIBRARY} @@ -127,7 +126,6 @@ target_link_libraries(blockchain_export PRIVATE cryptonote_core blockchain_db - p2p version epee ${Boost_FILESYSTEM_LIBRARY} @@ -150,7 +148,6 @@ target_link_libraries(blockchain_blackball wallet cryptonote_core blockchain_db - p2p version epee ${Boost_FILESYSTEM_LIBRARY} @@ -173,7 +170,6 @@ target_link_libraries(blockchain_usage PRIVATE cryptonote_core blockchain_db - p2p version epee ${Boost_FILESYSTEM_LIBRARY} diff --git a/src/blockchain_utilities/blockchain_blackball.cpp b/src/blockchain_utilities/blockchain_blackball.cpp index 1243822bb..95eb2f73d 100644 --- a/src/blockchain_utilities/blockchain_blackball.cpp +++ b/src/blockchain_utilities/blockchain_blackball.cpp @@ -28,8 +28,13 @@ #include <boost/range/adaptor/transformed.hpp> #include <boost/algorithm/string.hpp> +#include <boost/archive/portable_binary_iarchive.hpp> +#include <boost/archive/portable_binary_oarchive.hpp> +#include "common/unordered_containers_boost_serialization.h" #include "common/command_line.h" #include "common/varint.h" +#include "serialization/crypto.h" +#include "cryptonote_basic/cryptonote_boost_serialization.h" #include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" #include "cryptonote_core/blockchain.h" @@ -49,9 +54,17 @@ struct output_data { uint64_t amount; uint64_t index; + output_data(): amount(0), index(0) {} output_data(uint64_t a, uint64_t i): amount(a), index(i) {} bool operator==(const output_data &other) const { return other.amount == amount && other.index == index; } + template <typename t_archive> void serialize(t_archive &a, const unsigned int ver) + { + a & amount; + a & index; + } }; +BOOST_CLASS_VERSION(output_data, 0) + namespace std { template<> struct hash<output_data> @@ -64,8 +77,38 @@ namespace std return reinterpret_cast<const std::size_t &>(h); } }; + template<> struct hash<std::vector<uint64_t>> + { + size_t operator()(const std::vector<uint64_t> &v) const + { + crypto::hash h; + crypto::cn_fast_hash(v.data(), v.size() * sizeof(uint64_t), h); + return reinterpret_cast<const std::size_t &>(h); + } + }; } +struct blackball_state_t +{ + std::unordered_map<crypto::key_image, std::vector<uint64_t>> relative_rings; + std::unordered_map<output_data, std::unordered_set<crypto::key_image>> outputs; + std::unordered_map<std::string, uint64_t> processed_heights; + std::unordered_set<output_data> spent; + std::unordered_map<std::vector<uint64_t>, size_t> ring_instances; + + template <typename t_archive> void serialize(t_archive &a, const unsigned int ver) + { + a & relative_rings; + a & outputs; + a & processed_heights; + a & spent; + if (ver < 1) + return; + a & ring_instances; + } +}; +BOOST_CLASS_VERSION(blackball_state_t, 1) + static std::string get_default_db_path() { boost::filesystem::path dir = tools::get_default_data_dir(); @@ -75,7 +118,7 @@ static std::string get_default_db_path() return dir.string(); } -static bool for_all_transactions(const std::string &filename, const std::function<bool(const cryptonote::transaction_prefix&)> &f) +static bool for_all_transactions(const std::string &filename, uint64_t &start_idx, const std::function<bool(const cryptonote::transaction_prefix&)> &f) { MDB_env *env; MDB_dbi dbi; @@ -109,7 +152,9 @@ static bool for_all_transactions(const std::string &filename, const std::functio MDB_val v; bool fret = true; - MDB_cursor_op op = MDB_FIRST; + k.mv_size = sizeof(uint64_t); + k.mv_data = &start_idx; + MDB_cursor_op op = MDB_SET; while (1) { int ret = mdb_cursor_get(cur, &k, &v, op); @@ -119,6 +164,12 @@ static bool for_all_transactions(const std::string &filename, const std::functio if (ret) throw std::runtime_error("Failed to enumerate transactions: " + std::string(mdb_strerror(ret))); + if (k.mv_size != sizeof(uint64_t)) + throw std::runtime_error("Bad key size"); + const uint64_t idx = *(uint64_t*)k.mv_data; + if (idx < start_idx) + continue; + cryptonote::transaction_prefix tx; blobdata bd; bd.assign(reinterpret_cast<char*>(v.mv_data), v.mv_size); @@ -128,6 +179,7 @@ static bool for_all_transactions(const std::string &filename, const std::functio bool r = do_serialize(ba, tx); CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); + start_idx = *(uint64_t*)k.mv_data; if (!f(tx)) { fret = false; break; @@ -142,6 +194,24 @@ static bool for_all_transactions(const std::string &filename, const std::functio return fret; } +static std::vector<uint64_t> canonicalize(const std::vector<uint64_t> &v) +{ + std::vector<uint64_t> c; + c.reserve(v.size()); + c.push_back(v[0]); + for (size_t n = 1; n < v.size(); ++n) + { + if (v[n] != 0) + c.push_back(v[n]); + } + if (c.size() < v.size()) + { + MINFO("Ring has duplicate member(s): " << + boost::join(v | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); + } + return c; +} + int main(int argc, char* argv[]) { TRY_ENTRY(); @@ -308,17 +378,41 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Scanning for blackballable outputs..."); size_t done = 0; - std::unordered_map<crypto::key_image, std::vector<uint64_t>> relative_rings; - std::unordered_map<output_data, std::unordered_set<crypto::key_image>> outputs; - std::unordered_set<output_data> spent, newly_spent; + blackball_state_t state; + std::unordered_set<output_data> newly_spent; + const std::string state_file_path = (boost::filesystem::path(output_file_path) / "blackball-state.bin").string(); + + LOG_PRINT_L0("Loading state data from " << state_file_path); + std::ifstream state_data_in; + state_data_in.open(state_file_path, std::ios_base::binary | std::ios_base::in); + if (!state_data_in.fail()) + { + try + { + boost::archive::portable_binary_iarchive a(state_data_in); + a >> state; + } + catch (const std::exception &e) + { + MERROR("Failed to load state data from " << state_file_path << ", restarting from scratch"); + state = blackball_state_t(); + } + state_data_in.close(); + } + uint64_t start_blackballed_outputs = state.spent.size(); cryptonote::block b = core_storage[0]->get_db().get_block_from_height(0); tools::ringdb ringdb(output_file_path.string(), epee::string_tools::pod_to_hex(get_block_hash(b))); for (size_t n = 0; n < inputs.size(); ++n) { - LOG_PRINT_L0("Reading blockchain from " << inputs[n]); - for_all_transactions(inputs[n], [&](const cryptonote::transaction_prefix &tx)->bool + const std::string canonical = boost::filesystem::canonical(inputs[n]).string(); + uint64_t start_idx = 0; + auto it = state.processed_heights.find(canonical); + if (it != state.processed_heights.end()) + start_idx = it->second; + LOG_PRINT_L0("Reading blockchain from " << inputs[n] << " from " << start_idx); + for_all_transactions(inputs[n], start_idx, [&](const cryptonote::transaction_prefix &tx)->bool { for (const auto &in: tx.vin) { @@ -331,27 +425,39 @@ int main(int argc, char* argv[]) const std::vector<uint64_t> absolute = cryptonote::relative_output_offsets_to_absolute(txin.key_offsets); if (n == 0) for (uint64_t out: absolute) - outputs[output_data(txin.amount, out)].insert(txin.k_image); + state.outputs[output_data(txin.amount, out)].insert(txin.k_image); - std::vector<uint64_t> new_ring = txin.key_offsets; + std::vector<uint64_t> new_ring = canonicalize(txin.key_offsets); const uint32_t ring_size = txin.key_offsets.size(); + state.ring_instances[new_ring] += 1; if (ring_size == 1) { - const crypto::public_key pkey = core_storage[n]->get_output_key(txin.amount, txin.key_offsets[0]); + const crypto::public_key pkey = core_storage[n]->get_output_key(txin.amount, absolute[0]); MINFO("Blackballing output " << pkey << ", due to being used in a 1-ring"); ringdb.blackball(pkey); - newly_spent.insert(output_data(txin.amount, txin.key_offsets[0])); - spent.insert(output_data(txin.amount, txin.key_offsets[0])); + newly_spent.insert(output_data(txin.amount, absolute[0])); + state.spent.insert(output_data(txin.amount, absolute[0])); } - else if (relative_rings.find(txin.k_image) != relative_rings.end()) + else if (state.ring_instances[new_ring] == new_ring.size()) + { + for (size_t o = 0; o < new_ring.size(); ++o) + { + const crypto::public_key pkey = core_storage[n]->get_output_key(txin.amount, absolute[o]); + MINFO("Blackballing output " << pkey << ", due to being used in " << new_ring.size() << " identical " << new_ring.size() << "-rings"); + ringdb.blackball(pkey); + newly_spent.insert(output_data(txin.amount, absolute[o])); + state.spent.insert(output_data(txin.amount, absolute[o])); + } + } + else if (state.relative_rings.find(txin.k_image) != state.relative_rings.end()) { MINFO("Key image " << txin.k_image << " already seen: rings " << - boost::join(relative_rings[txin.k_image] | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ") << + boost::join(state.relative_rings[txin.k_image] | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ") << ", " << boost::join(txin.key_offsets | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); - if (relative_rings[txin.k_image] != txin.key_offsets) + if (state.relative_rings[txin.k_image] != txin.key_offsets) { MINFO("Rings are different"); - const std::vector<uint64_t> r0 = cryptonote::relative_output_offsets_to_absolute(relative_rings[txin.k_image]); + const std::vector<uint64_t> r0 = cryptonote::relative_output_offsets_to_absolute(state.relative_rings[txin.k_image]); const std::vector<uint64_t> r1 = cryptonote::relative_output_offsets_to_absolute(txin.key_offsets); std::vector<uint64_t> common; for (uint64_t out: r0) @@ -369,7 +475,7 @@ int main(int argc, char* argv[]) MINFO("Blackballing output " << pkey << ", due to being used in rings with a single common element"); ringdb.blackball(pkey); newly_spent.insert(output_data(txin.amount, common[0])); - spent.insert(output_data(txin.amount, common[0])); + state.spent.insert(output_data(txin.amount, common[0])); } else { @@ -381,10 +487,11 @@ int main(int argc, char* argv[]) } } } - relative_rings[txin.k_image] = new_ring; + state.relative_rings[txin.k_image] = new_ring; } return true; }); + state.processed_heights[canonical] = start_idx; } while (!newly_spent.empty()) @@ -395,15 +502,15 @@ int main(int argc, char* argv[]) for (const output_data &od: work_spent) { - for (const crypto::key_image &ki: outputs[od]) + for (const crypto::key_image &ki: state.outputs[od]) { - std::vector<uint64_t> absolute = cryptonote::relative_output_offsets_to_absolute(relative_rings[ki]); + std::vector<uint64_t> absolute = cryptonote::relative_output_offsets_to_absolute(state.relative_rings[ki]); size_t known = 0; uint64_t last_unknown = 0; for (uint64_t out: absolute) { output_data new_od(od.amount, out); - if (spent.find(new_od) != spent.end()) + if (state.spent.find(new_od) != state.spent.end()) ++known; else last_unknown = out; @@ -415,12 +522,31 @@ int main(int argc, char* argv[]) absolute.size() << "-ring where all other outputs are known to be spent"); ringdb.blackball(pkey); newly_spent.insert(output_data(od.amount, last_unknown)); - spent.insert(output_data(od.amount, last_unknown)); + state.spent.insert(output_data(od.amount, last_unknown)); } } } } + LOG_PRINT_L0("Saving state data to " << state_file_path); + std::ofstream state_data_out; + state_data_out.open(state_file_path, std::ios_base::binary | std::ios_base::out | std::ios::trunc); + if (!state_data_out.fail()) + { + try + { + boost::archive::portable_binary_oarchive a(state_data_out); + a << state; + } + catch (const std::exception &e) + { + MERROR("Failed to save state data to " << state_file_path); + } + state_data_out.close(); + } + + uint64_t diff = state.spent.size() - start_blackballed_outputs; + LOG_PRINT_L0(std::to_string(diff) << " new outputs blackballed, " << state.spent.size() << " total outputs blackballed"); LOG_PRINT_L0("Blockchain blackball data exported OK"); return 0; diff --git a/src/blockchain_utilities/blockchain_usage.cpp b/src/blockchain_utilities/blockchain_usage.cpp index b78f3591b..38a0b2648 100644 --- a/src/blockchain_utilities/blockchain_usage.cpp +++ b/src/blockchain_utilities/blockchain_usage.cpp @@ -243,10 +243,17 @@ int main(int argc, char* argv[]) counts[out.second.size()]++; total++; } - for (const auto &c: counts) + if (total > 0) { - float percent = 100.f * c.second / total; - MINFO(std::to_string(c.second) << " outputs used " << c.first << " times (" << percent << "%)"); + for (const auto &c: counts) + { + float percent = 100.f * c.second / total; + MINFO(std::to_string(c.second) << " outputs used " << c.first << " times (" << percent << "%)"); + } + } + else + { + MINFO("No outputs to process"); } LOG_PRINT_L0("Blockchain usage exported OK"); diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex 501a55673..085558504 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp index 51e071577..fb238dca7 100644 --- a/src/common/threadpool.cpp +++ b/src/common/threadpool.cpp @@ -39,11 +39,11 @@ static __thread int depth = 0; namespace tools { -threadpool::threadpool() : running(true), active(0) { +threadpool::threadpool(unsigned int max_threads) : running(true), active(0) { boost::thread::attributes attrs; attrs.set_stack_size(THREAD_STACK_SIZE); - max = tools::get_max_concurrency(); - size_t i = max; + max = max_threads ? max_threads : tools::get_max_concurrency(); + unsigned int i = max; while(i--) { threads.push_back(boost::thread(attrs, boost::bind(&threadpool::run, this))); } @@ -78,7 +78,7 @@ void threadpool::submit(waiter *obj, std::function<void()> f) { } } -int threadpool::get_max_concurrency() { +unsigned int threadpool::get_max_concurrency() const { return max; } diff --git a/src/common/threadpool.h b/src/common/threadpool.h index 34152541c..bf80a87f6 100644 --- a/src/common/threadpool.h +++ b/src/common/threadpool.h @@ -46,6 +46,9 @@ public: static threadpool instance; return instance; } + static threadpool *getNewForUnitTests(unsigned max_threads = 0) { + return new threadpool(max_threads); + } // The waiter lets the caller know when all of its // tasks are completed. @@ -66,11 +69,12 @@ public: // task to finish. void submit(waiter *waiter, std::function<void()> f); - int get_max_concurrency(); + unsigned int get_max_concurrency() const; + + ~threadpool(); private: - threadpool(); - ~threadpool(); + threadpool(unsigned int max_threads = 0); typedef struct entry { waiter *wo; std::function<void()> f; @@ -79,8 +83,8 @@ public: boost::condition_variable has_work; boost::mutex mutex; std::vector<boost::thread> threads; - int active; - int max; + unsigned int active; + unsigned int max; bool running; void run(); }; diff --git a/src/common/util.cpp b/src/common/util.cpp index 7e77e19b1..008610117 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -34,6 +34,17 @@ #include <gnu/libc-version.h> #endif +#ifdef __GLIBC__ +#include <sys/types.h> +#include <sys/stat.h> +#include <ustat.h> +#include <unistd.h> +#include <dirent.h> +#include <string.h> +#include <ctype.h> +#include <string> +#endif + #include "unbound.h" #include "include_base_utils.h" @@ -632,6 +643,65 @@ std::string get_nix_version_display_string() #endif } + bool is_hdd(const char *path) + { +#ifdef __GLIBC__ + std::string device = ""; + struct stat st, dst; + if (stat(path, &st) < 0) + return 0; + + DIR *dir = opendir("/dev/block"); + if (!dir) + return 0; + struct dirent *de; + while ((de = readdir(dir))) + { + if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) + { + std::string dev_path = std::string("/dev/block/") + de->d_name; + char resolved[PATH_MAX]; + if (realpath(dev_path.c_str(), resolved) && !strncmp(resolved, "/dev/", 5)) + { + if (stat(resolved, &dst) == 0) + { + if (dst.st_rdev == st.st_dev) + { + // take out trailing digits (eg, sda1 -> sda) + char *ptr = resolved; + while (*ptr) + ++ptr; + while (ptr > resolved && isdigit(*--ptr)) + *ptr = 0; + device = resolved + 5; + break; + } + } + } + } + } + closedir(dir); + + if (device.empty()) + return 0; + + std::string sys_path = "/sys/block/" + device + "/queue/rotational"; + FILE *f = fopen(sys_path.c_str(), "r"); + if (!f) + return false; + char s[8]; + char *ptr = fgets(s, sizeof(s), f); + fclose(f); + if (!ptr) + return 0; + s[sizeof(s) - 1] = 0; + int n = atoi(s); // returns 0 on parse error + return n == 1; +#else + return 0; +#endif + } + namespace { boost::mutex max_concurrency_lock; diff --git a/src/common/util.h b/src/common/util.h index d3ba47a4f..7caf0e3c5 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -212,4 +212,6 @@ namespace tools bool sha256sum(const uint8_t *data, size_t len, crypto::hash &hash); bool sha256sum(const std::string &filename, crypto::hash &hash); + + bool is_hdd(const char *path); } diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index ba0149240..f4ef751d3 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -70,8 +70,6 @@ namespace crypto { #include "random.h" } - boost::mutex random_lock; - static inline unsigned char *operator &(ec_point &point) { return &reinterpret_cast<unsigned char &>(point); } @@ -88,6 +86,13 @@ namespace crypto { return &reinterpret_cast<const unsigned char &>(scalar); } + void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes) + { + static boost::mutex random_lock; + boost::lock_guard<boost::mutex> lock(random_lock); + generate_random_bytes_not_thread_safe(N, bytes); + } + /* generate a random 32-byte (256-bit) integer and copy it to res */ static inline void random_scalar_not_thread_safe(ec_scalar &res) { unsigned char tmp[64]; @@ -96,8 +101,10 @@ namespace crypto { memcpy(&res, tmp, 32); } static inline void random_scalar(ec_scalar &res) { - boost::lock_guard<boost::mutex> lock(random_lock); - random_scalar_not_thread_safe(res); + unsigned char tmp[64]; + generate_random_bytes_thread_safe(64, tmp); + sc_reduce(tmp); + memcpy(&res, tmp, 32); } void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 81ebfb9e2..9ea0f2ec0 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -53,8 +53,6 @@ namespace crypto { #include "random.h" } - extern boost::mutex random_lock; - #pragma pack(push, 1) POD_CLASS ec_point { char data[32]; @@ -149,11 +147,12 @@ namespace crypto { const public_key *const *, std::size_t, const signature *); }; + void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes); + /* Generate N random bytes */ inline void rand(size_t N, uint8_t *bytes) { - boost::lock_guard<boost::mutex> lock(random_lock); - generate_random_bytes_not_thread_safe(N, bytes); + generate_random_bytes_thread_safe(N, bytes); } /* Generate a value filled with random bytes. @@ -161,8 +160,7 @@ namespace crypto { template<typename T> typename std::enable_if<std::is_pod<T>::value, T>::type rand() { typename std::remove_cv<T>::type res; - boost::lock_guard<boost::mutex> lock(random_lock); - generate_random_bytes_not_thread_safe(sizeof(T), &res); + generate_random_bytes_thread_safe(sizeof(T), (uint8_t*)&res); return res; } diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index bab991d19..aac6ec22b 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -157,7 +157,7 @@ DISABLE_VS_WARNINGS(4244 4345) void account_base::create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey) { crypto::secret_key fake; - memset(&fake, 0, sizeof(fake)); + memset(&unwrap(fake), 0, sizeof(fake)); create_from_keys(address, fake, viewkey); } //----------------------------------------------------------------- diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index 5cd1709ab..3f4651565 100644 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -67,15 +67,15 @@ namespace cryptonote switch (s) { case cryptonote_connection_context::state_before_handshake: - return "state_before_handshake"; + return "before_handshake"; case cryptonote_connection_context::state_synchronizing: - return "state_synchronizing"; + return "synchronizing"; case cryptonote_connection_context::state_standby: - return "state_standby"; + return "standby"; case cryptonote_connection_context::state_idle: - return "state_idle"; + return "idle"; case cryptonote_connection_context::state_normal: - return "state_normal"; + return "normal"; default: return "unknown"; } diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index 95f1ecab9..f05b25901 100644 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -379,20 +379,24 @@ uint8_t HardFork::get_ideal_version(uint64_t height) const uint64_t HardFork::get_earliest_ideal_height_for_version(uint8_t version) const { - for (unsigned int n = heights.size() - 1; n > 0; --n) { - if (heights[n].version <= version) - return heights[n].height; + uint64_t height = std::numeric_limits<uint64_t>::max(); + for (auto i = heights.rbegin(); i != heights.rend(); ++i) { + if (i->version >= version) { + height = i->height; + } else { + break; + } } - return 0; + return height; } uint8_t HardFork::get_next_version() const { CRITICAL_REGION_LOCAL(lock); uint64_t height = db.height(); - for (unsigned int n = heights.size() - 1; n > 0; --n) { - if (height >= heights[n].height) { - return heights[n < heights.size() - 1 ? n + 1 : n].version; + for (auto i = heights.rbegin(); i != heights.rend(); ++i) { + if (height >= i->height) { + return (i == heights.rbegin() ? i : (i - 1))->version; } } return original_version; diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 2c777f5a2..3a3222f9b 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -328,6 +328,11 @@ namespace cryptonote LOG_PRINT_L0("Background mining controller thread started" ); } + if(get_ignore_battery()) + { + MINFO("Ignoring battery"); + } + return true; } //----------------------------------------------------------------------------------------------------- diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 2638a724b..38f7f94e1 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -807,16 +807,18 @@ difficulty_type Blockchain::get_difficulty_for_next_block() { LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_difficulty_lock); - // we can call this without the blockchain lock, it might just give us - // something a bit out of date, but that's fine since anything which - // requires the blockchain lock will have acquired it in the first place, - // and it will be unlocked only when called from the getinfo RPC crypto::hash top_hash = get_tail_id(); - if (top_hash == m_difficulty_for_next_block_top_hash) - return m_difficulty_for_next_block; + { + CRITICAL_REGION_LOCAL(m_difficulty_lock); + // we can call this without the blockchain lock, it might just give us + // something a bit out of date, but that's fine since anything which + // requires the blockchain lock will have acquired it in the first place, + // and it will be unlocked only when called from the getinfo RPC + if (top_hash == m_difficulty_for_next_block_top_hash) + return m_difficulty_for_next_block; + } - CRITICAL_REGION_LOCAL1(m_blockchain_lock); + CRITICAL_REGION_LOCAL(m_blockchain_lock); std::vector<uint64_t> timestamps; std::vector<difficulty_type> difficulties; auto height = m_db->height(); @@ -825,7 +827,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block() // then when the next block difficulty is queried, push the latest height data and // pop the oldest one from the list. This only requires 1x read per height instead // of doing 735 (DIFFICULTY_BLOCKS_COUNT). - if (m_timestamps_and_difficulties_height != 0 && ((height - m_timestamps_and_difficulties_height) == 1)) + if (m_timestamps_and_difficulties_height != 0 && ((height - m_timestamps_and_difficulties_height) == 1) && m_timestamps.size() >= DIFFICULTY_BLOCKS_COUNT) { uint64_t index = height - 1; m_timestamps.push_back(m_db->get_block_timestamp(index)); @@ -860,6 +862,8 @@ difficulty_type Blockchain::get_difficulty_for_next_block() } size_t target = get_difficulty_target(); difficulty_type diff = next_difficulty(timestamps, difficulties, target); + + CRITICAL_REGION_LOCAL1(m_difficulty_lock); m_difficulty_for_next_block_top_hash = top_hash; m_difficulty_for_next_block = diff; return diff; @@ -1210,6 +1214,12 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m b.prev_id = get_tail_id(); b.timestamp = time(NULL); + uint64_t median_ts; + if (!check_block_timestamp(b, median_ts)) + { + b.timestamp = median_ts; + } + diffic = get_difficulty_for_next_block(); CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead."); @@ -2085,16 +2095,19 @@ bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container { try { - blocks.push_back(std::make_pair(m_db->get_block_blob(block_hash), block())); - if (!parse_and_validate_block_from_blob(blocks.back().first, blocks.back().second)) + uint64_t height = 0; + if (m_db->block_exists(block_hash, &height)) { - LOG_ERROR("Invalid block"); - return false; + blocks.push_back(std::make_pair(m_db->get_block_blob_from_height(height), block())); + if (!parse_and_validate_block_from_blob(blocks.back().first, blocks.back().second)) + { + LOG_ERROR("Invalid block: " << block_hash); + blocks.pop_back(); + missed_bs.push_back(block_hash); + } } - } - catch (const BLOCK_DNE& e) - { - missed_bs.push_back(block_hash); + else + missed_bs.push_back(block_hash); } catch (const std::exception& e) { @@ -2272,19 +2285,19 @@ bool Blockchain::have_block(const crypto::hash& id) const if(m_db->block_exists(id)) { - LOG_PRINT_L3("block exists in main chain"); + LOG_PRINT_L2("block " << id << " found in main chain"); return true; } if(m_alternative_chains.count(id)) { - LOG_PRINT_L3("block found in m_alternative_chains"); + LOG_PRINT_L2("block " << id << " found in m_alternative_chains"); return true; } if(m_invalid_blocks.count(id)) { - LOG_PRINT_L3("block found in m_invalid_blocks"); + LOG_PRINT_L2("block " << id << " found in m_invalid_blocks"); return true; } @@ -2713,6 +2726,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter; + const auto waiter_guard = epee::misc_utils::create_scope_leave_handler([&]() { waiter.wait(); }); int threads = tpool.get_max_concurrency(); for (const auto& txin : tx.vin) @@ -3182,10 +3196,10 @@ uint64_t Blockchain::get_adjusted_time() const } //------------------------------------------------------------------ //TODO: revisit, has changed a bit on upstream -bool Blockchain::check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b) const +bool Blockchain::check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b, uint64_t& median_ts) const { LOG_PRINT_L3("Blockchain::" << __func__); - uint64_t median_ts = epee::misc_utils::median(timestamps); + median_ts = epee::misc_utils::median(timestamps); if(b.timestamp < median_ts) { @@ -3203,7 +3217,7 @@ bool Blockchain::check_block_timestamp(std::vector<uint64_t>& timestamps, const // true if the block's timestamp is not less than the timestamp of the // median of the selected blocks // false otherwise -bool Blockchain::check_block_timestamp(const block& b) const +bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) const { LOG_PRINT_L3("Blockchain::" << __func__); if(b.timestamp > get_adjusted_time() + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT) @@ -3228,7 +3242,7 @@ bool Blockchain::check_block_timestamp(const block& b) const timestamps.push_back(m_db->get_block_timestamp(offset)); } - return check_block_timestamp(timestamps, b); + return check_block_timestamp(timestamps, b, median_ts); } //------------------------------------------------------------------ void Blockchain::return_tx_to_pool(std::vector<transaction> &txs) @@ -3922,7 +3936,7 @@ uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::list<c // add to the known hashes array if (!valid) { - MWARNING("invalid hash for blocks " << n * HASH_OF_HASHES_STEP << " - " << (n * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP - 1)); + MDEBUG("invalid hash for blocks " << n * HASH_OF_HASHES_STEP << " - " << (n * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP - 1)); break; } @@ -4420,7 +4434,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "59261c03b54bcb21bd463f9fe40a94f40840a12642e9a3b3bfb11b35839a5fe3"; +static const char expected_block_hashes_hash[] = "0924bc1c47aae448321fde949554be192878dd800e6489379865218f84eacbca"; void Blockchain::load_compiled_in_block_hashes() { const bool testnet = m_nettype == TESTNET; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 3dd9a6d69..ef736d1e7 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -795,6 +795,13 @@ namespace cryptonote uint8_t get_hard_fork_version(uint64_t height) const { return m_hardfork->get(height); } /** + * @brief returns the earliest block a given version may activate + * + * @return the height + */ + uint64_t get_earliest_ideal_height_for_version(uint8_t version) const { return m_hardfork->get_earliest_ideal_height_for_version(version); } + + /** * @brief get information about hardfork voting for a version * * @param version the version in question @@ -1300,10 +1307,12 @@ namespace cryptonote * false otherwise * * @param b the block to be checked + * @param median_ts return-by-reference the median of timestamps * * @return true if the block's timestamp is valid, otherwise false */ - bool check_block_timestamp(const block& b) const; + bool check_block_timestamp(const block& b, uint64_t& median_ts) const; + bool check_block_timestamp(const block& b) const { uint64_t median_ts; return check_block_timestamp(b, median_ts); } /** * @brief checks a block's timestamp @@ -1316,7 +1325,8 @@ namespace cryptonote * * @return true if the block's timestamp is valid, otherwise false */ - bool check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b) const; + bool check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b, uint64_t& median_ts) const; + bool check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b) const { uint64_t median_ts; return check_block_timestamp(timestamps, b, median_ts); } /** * @brief get the "adjusted time" diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index c0d82d556..7fc81a87d 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -170,7 +170,6 @@ namespace cryptonote m_last_dns_checkpoints_update(0), m_last_json_checkpoints_update(0), m_disable_dns_checkpoints(false), - m_threadpool(tools::threadpool::getInstance()), m_update_download(0), m_nettype(UNDEFINED) { @@ -438,6 +437,7 @@ namespace cryptonote std::vector<std::string> options; boost::trim(db_sync_mode); boost::split(options, db_sync_mode, boost::is_any_of(" :")); + const bool db_sync_mode_is_default = command_line::is_arg_defaulted(vm, cryptonote::arg_db_sync_mode); for(const auto &option : options) MDEBUG("option: " << option); @@ -458,18 +458,18 @@ namespace cryptonote { safemode = true; db_flags = DBF_SAFE; - sync_mode = db_nosync; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_nosync; } else if(options[0] == "fast") { db_flags = DBF_FAST; - sync_mode = db_async; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async; } else if(options[0] == "fastest") { db_flags = DBF_FASTEST; blocks_per_sync = 1000; // default to fastest:async:1000 - sync_mode = db_async; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async; } else db_flags = DEFAULT_FLAGS; @@ -478,9 +478,9 @@ namespace cryptonote if(options.size() >= 2 && !safemode) { if(options[1] == "sync") - sync_mode = db_sync; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_sync; else if(options[1] == "async") - sync_mode = db_async; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async; } if(options.size() >= 3 && !safemode) @@ -675,15 +675,17 @@ namespace cryptonote bool core::handle_incoming_txs(const std::list<blobdata>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { TRY_ENTRY(); + CRITICAL_REGION_LOCAL(m_incoming_tx_lock); struct result { bool res; cryptonote::transaction tx; crypto::hash hash; crypto::hash prefix_hash; bool in_txpool; bool in_blockchain; }; std::vector<result> results(tx_blobs.size()); tvc.resize(tx_blobs.size()); + tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter; std::list<blobdata>::const_iterator it = tx_blobs.begin(); for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { - m_threadpool.submit(&waiter, [&, i, it] { + tpool.submit(&waiter, [&, i, it] { try { results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); @@ -710,7 +712,7 @@ namespace cryptonote } else { - m_threadpool.submit(&waiter, [&, i, it] { + tpool.submit(&waiter, [&, i, it] { try { results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); @@ -1400,6 +1402,11 @@ namespace cryptonote return get_blockchain_storage().get_hard_fork_version(height); } //----------------------------------------------------------------------------------------------- + uint64_t core::get_earliest_ideal_height_for_version(uint8_t version) const + { + return get_blockchain_storage().get_earliest_ideal_height_for_version(version); + } + //----------------------------------------------------------------------------------------------- bool core::check_updates() { static const char software[] = "monero"; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 17b5680e5..91bd50729 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -39,7 +39,6 @@ #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "storages/portable_storage_template_helper.h" #include "common/download.h" -#include "common/threadpool.h" #include "common/command_line.h" #include "tx_pool.h" #include "blockchain.h" @@ -663,6 +662,13 @@ namespace cryptonote uint8_t get_hard_fork_version(uint64_t height) const; /** + * @brief return the earliest block a given version may activate + * + * @return what it says above + */ + uint64_t get_earliest_ideal_height_for_version(uint8_t version) const; + + /** * @brief gets start_time * */ @@ -984,8 +990,6 @@ namespace cryptonote std::unordered_set<crypto::hash> bad_semantics_txes[2]; boost::mutex bad_semantics_txes_lock; - tools::threadpool& m_threadpool; - enum { UPDATES_DISABLED, UPDATES_NOTIFY, diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 711605597..2e1df8078 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -308,7 +308,8 @@ namespace cryptonote << " [Your node is " << abs_diff << " blocks (" << ((abs_diff - diff_v2) / (24 * 60 * 60 / DIFFICULTY_TARGET_V1)) + (diff_v2 / (24 * 60 * 60 / DIFFICULTY_TARGET_V2)) << " days) " << (0 <= diff ? std::string("behind") : std::string("ahead")) << "] " << ENDL << "SYNCHRONIZATION started"); - m_core.safesyncmode(false); + if (hshd.current_height >= m_core.get_current_blockchain_height() + 5) // don't switch to unsafe mode just for a few blocks + m_core.safesyncmode(false); } LOG_PRINT_L1("Remote blockchain height: " << hshd.current_height << ", id: " << hshd.top_id); context.m_state = cryptonote_connection_context::state_synchronizing; @@ -1629,10 +1630,10 @@ skip: } uint64_t n_use_blocks = m_core.prevalidate_block_hashes(arg.start_height, arg.m_block_ids); - if (n_use_blocks == 0) + if (n_use_blocks + HASH_OF_HASHES_STEP <= arg.m_block_ids.size()) { - LOG_ERROR_CCONTEXT("Peer yielded no usable blocks, dropping connection"); - drop_connection(context, false, false); + LOG_ERROR_CCONTEXT("Most blocks are invalid, dropping connection"); + drop_connection(context, true, false); return 1; } diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 8b51b9b85..aa688294d 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "common/dns_utils.h" +#include "common/command_line.h" #include "version.h" #include "daemon/command_parser_executor.h" @@ -315,7 +316,7 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg return true; } if(nettype != cryptonote::MAINNET) - std::cout << "Mining to a " << (nettype == cryptonote::TESTNET ? "testnet" : "stagenet") << "address, make sure this is intentional!" << std::endl; + std::cout << "Mining to a " << (nettype == cryptonote::TESTNET ? "testnet" : "stagenet") << " address, make sure this is intentional!" << std::endl; uint64_t threads_count = 1; bool do_background_mining = false; bool ignore_battery = false; @@ -326,12 +327,26 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg if(args.size() == 4) { - ignore_battery = args[3] == "true"; + if(args[3] == "true" || command_line::is_yes(args[3]) || args[3] == "1") + { + ignore_battery = true; + } + else if(args[3] != "false" && !command_line::is_no(args[3]) && args[3] != "0") + { + return false; + } } if(args.size() >= 3) { - do_background_mining = args[2] == "true"; + if(args[2] == "true" || command_line::is_yes(args[2]) || args[2] == "1") + { + do_background_mining = true; + } + else if(args[2] != "false" && !command_line::is_no(args[2]) && args[2] != "0") + { + return false; + } } if(args.size() >= 2) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 2efb501ea..956c84a01 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -74,6 +74,7 @@ namespace { << "depth: " << boost::lexical_cast<std::string>(header.depth) << std::endl << "hash: " << header.hash << std::endl << "difficulty: " << boost::lexical_cast<std::string>(header.difficulty) << std::endl + << "POW hash: " << header.pow_hash << std::endl << "reward: " << boost::lexical_cast<std::string>(header.reward); } @@ -654,6 +655,7 @@ bool t_rpc_command_executor::print_block_by_hash(crypto::hash block_hash) { epee::json_rpc::error error_resp; req.hash = epee::string_tools::pod_to_hex(block_hash); + req.fill_pow_hash = true; std::string fail_message = "Unsuccessful"; @@ -685,6 +687,7 @@ bool t_rpc_command_executor::print_block_by_height(uint64_t height) { epee::json_rpc::error error_resp; req.height = height; + req.fill_pow_hash = true; std::string fail_message = "Unsuccessful"; @@ -973,7 +976,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() { } else { - memset(&res.pool_stats, 0, sizeof(res.pool_stats)); + res.pool_stats = {}; if (!m_rpc_server->on_get_transaction_pool_stats(req, res, false) || res.status != CORE_RPC_STATUS_OK) { tools::fail_msg_writer() << make_error(fail_message, res.status); @@ -1892,7 +1895,7 @@ bool t_rpc_command_executor::sync_info() for (const auto &s: res.spans) if (s.rate > 0.0f && s.connection_id == p.info.connection_id) nblocks += s.nblocks, size += s.size; - tools::success_msg_writer() << address << " " << epee::string_tools::pad_string(p.info.peer_id, 16, '0', true) << " " << p.info.height << " " << p.info.current_download << " kB/s, " << nblocks << " blocks / " << size/1e6 << " MB queued"; + tools::success_msg_writer() << address << " " << epee::string_tools::pad_string(p.info.peer_id, 16, '0', true) << " " << epee::string_tools::pad_string(p.info.state, 16) << " " << p.info.height << " " << p.info.current_download << " kB/s, " << nblocks << " blocks / " << size/1e6 << " MB queued"; } uint64_t total_size = 0; diff --git a/src/debug_utilities/cn_deserialize.cpp b/src/debug_utilities/cn_deserialize.cpp index dfbd3b864..6c09b0f18 100644 --- a/src/debug_utilities/cn_deserialize.cpp +++ b/src/debug_utilities/cn_deserialize.cpp @@ -27,6 +27,8 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <boost/filesystem.hpp> +#include <boost/algorithm/string/join.hpp> +#include <boost/range/adaptor/transformed.hpp> #include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/tx_extra.h" #include "cryptonote_core/blockchain.h" @@ -51,6 +53,7 @@ static void print_extra_fields(const std::vector<cryptonote::tx_extra_field> &fi else if (typeid(cryptonote::tx_extra_pub_key) == fields[n].type()) std::cout << "extra pub key: " << boost::get<cryptonote::tx_extra_pub_key>(fields[n]).pub_key; else if (typeid(cryptonote::tx_extra_nonce) == fields[n].type()) std::cout << "extra nonce: " << epee::string_tools::buff_to_hex_nodelimer(boost::get<cryptonote::tx_extra_nonce>(fields[n]).nonce); else if (typeid(cryptonote::tx_extra_merge_mining_tag) == fields[n].type()) std::cout << "extra merge mining tag: depth " << boost::get<cryptonote::tx_extra_merge_mining_tag>(fields[n]).depth << ", merkle root " << boost::get<cryptonote::tx_extra_merge_mining_tag>(fields[n]).merkle_root; + else if (typeid(cryptonote::tx_extra_additional_pub_keys) == fields[n].type()) std::cout << "additional tx pubkeys: " << boost::join(boost::get<cryptonote::tx_extra_additional_pub_keys>(fields[n]).data | boost::adaptors::transformed([](const crypto::public_key &key){ return epee::string_tools::pod_to_hex(key); }), ", " ); else if (typeid(cryptonote::tx_extra_mysterious_minergate) == fields[n].type()) std::cout << "extra minergate custom: " << epee::string_tools::buff_to_hex_nodelimer(boost::get<cryptonote::tx_extra_mysterious_minergate>(fields[n]).data); else std::cout << "unknown"; std::cout << std::endl; diff --git a/src/debug_utilities/object_sizes.cpp b/src/debug_utilities/object_sizes.cpp index a3d037a59..ab8839636 100644 --- a/src/debug_utilities/object_sizes.cpp +++ b/src/debug_utilities/object_sizes.cpp @@ -94,6 +94,11 @@ int main(int argc, char* argv[]) SL(nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>>); SL(nodetool::p2p_connection_context_t<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>::connection_context>); SL(nodetool::network_address_old); + SL(nodetool::peerlist_entry_base<nodetool::network_address_old>); + + SL(nodetool::network_config); + SL(nodetool::basic_node_data); + SL(cryptonote::CORE_SYNC_DATA); SL(tools::wallet2::transfer_details); SL(tools::wallet2::payment_details); diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 3b9ab6744..aedaf8382 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -48,6 +48,15 @@ namespace hw { /* ===================================================================== */ /* === Debug ==== */ /* ===================================================================== */ + #ifdef WIN32 + static char *pcsc_stringify_error(LONG rv) { + static __thread char out[20]; + sprintf_s(out, sizeof(out), "0x%08lX", rv); + + return out; + } + #endif + void set_apdu_verbose(bool verbose) { apdu_verbose = verbose; } diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index f1fcaab87..b62bdf959 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -33,8 +33,13 @@ #include <cstddef> #include <string> #include "device.hpp" +#ifdef WIN32 +#include <winscard.h> +#define MAX_ATR_SIZE 33 +#else #include <PCSC/winscard.h> #include <PCSC/wintypes.h> +#endif #include <boost/thread/mutex.hpp> #include <boost/thread/recursive_mutex.hpp> diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index 943589b4a..03e0a7946 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -174,7 +174,9 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_stagenet); command_line::add_arg(desc_params, arg_create_address_file); - const auto vm = wallet_args::main( + boost::optional<po::variables_map> vm; + bool should_terminate = false; + std::tie(vm, should_terminate) = wallet_args::main( argc, argv, "monero-gen-multisig [(--testnet|--stagenet)] [--filename-base=<filename>] [--scheme=M/N] [--threshold=M] [--participants=N]", genms::tr("This program generates a set of multisig wallets - use this simpler scheme only if all the participants trust each other"), @@ -185,6 +187,8 @@ int main(int argc, char* argv[]) ); if (!vm) return 1; + if (should_terminate) + return 0; bool testnet, stagenet; uint32_t threshold = 0, total = 0; diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index 6a2a3e0c4..7dd09ecb9 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -67,6 +67,9 @@ #include "language_base.h" #include "singleton.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "mnemonic" + namespace { uint32_t create_checksum_index(const std::vector<std::string> &word_list, @@ -152,6 +155,7 @@ namespace if (full_match) { *language = *it1; + MINFO("Full match for language " << (*language)->get_english_language_name()); return true; } // Some didn't match. Clear the index array. @@ -164,9 +168,11 @@ namespace if (fallback) { *language = fallback; + MINFO("Fallback match for language " << (*language)->get_english_language_name()); return true; } + MINFO("No match found"); return false; } @@ -217,7 +223,9 @@ namespace checksum; std::string trimmed_last_word = last_word.length() > unique_prefix_length ? Language::utf8prefix(last_word, unique_prefix_length) : last_word; - return trimmed_checksum == trimmed_last_word; + bool ret = trimmed_checksum == trimmed_last_word; + MINFO("Checksum is %s" << (ret ? "valid" : "invalid")); + return ret; } } @@ -253,7 +261,10 @@ namespace crypto boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on); if (len % 4) + { + MERROR("Invalid seed: not a multiple of 4"); return false; + } bool has_checksum = true; if (len) @@ -263,6 +274,7 @@ namespace crypto if (seed.size() != expected/2 && seed.size() != expected && seed.size() != expected + 1) { + MERROR("Invalid seed: unexpected number of words"); return false; } @@ -274,6 +286,7 @@ namespace crypto Language::Base *language; if (!find_seed_language(seed, has_checksum, matched_indices, &language)) { + MERROR("Invalid seed: language not found"); return false; } language_name = language->get_language_name(); @@ -284,6 +297,7 @@ namespace crypto if (!checksum_test(seed, language->get_unique_prefix_length())) { // Checksum fail + MERROR("Invalid seed: invalid checksum"); return false; } seed.pop_back(); @@ -300,7 +314,11 @@ namespace crypto val = w1 + word_list_length * (((word_list_length - w1) + w2) % word_list_length) + word_list_length * word_list_length * (((word_list_length - w2) + w3) % word_list_length); - if (!(val % word_list_length == w1)) return false; + if (!(val % word_list_length == w1)) + { + MERROR("Invalid seed: mumble mumble"); + return false; + } dst.append((const char*)&val, 4); // copy 4 bytes to position } @@ -332,9 +350,15 @@ namespace crypto { std::string s; if (!words_to_bytes(words, s, sizeof(dst), true, language_name)) + { + MERROR("Invalid seed: failed to convert words to bytes"); return false; + } if (s.size() != sizeof(dst)) + { + MERROR("Invalid seed: wrong output size"); return false; + } dst = *(const crypto::secret_key*)s.data(); return true; } diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 4606f66ee..90e2f78b1 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -244,6 +244,7 @@ namespace nodetool bool check_connection_and_handshake_with_peer(const epee::net_utils::network_address& na, uint64_t last_seen_stamp); bool gray_peerlist_housekeeping(); + bool check_incoming_connections(); void kill() { ///< will be called e.g. from deinit() _info("Killing the net_node"); @@ -307,6 +308,7 @@ namespace nodetool epee::math_helper::once_a_time_seconds<1> m_connections_maker_interval; epee::math_helper::once_a_time_seconds<60*30, false> m_peerlist_store_interval; epee::math_helper::once_a_time_seconds<60> m_gray_peerlist_housekeeping_interval; + epee::math_helper::once_a_time_seconds<900, false> m_incoming_connections_interval; std::string m_bind_ip; std::string m_port; @@ -316,6 +318,7 @@ namespace nodetool std::list<epee::net_utils::network_address> m_priority_peers; std::vector<epee::net_utils::network_address> m_exclusive_peers; std::vector<epee::net_utils::network_address> m_seed_nodes; + bool m_fallback_seed_nodes_added; std::list<nodetool::peerlist_entry> m_command_line_peers; uint64_t m_peer_livetime; //keep connections to initiate some interactions diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 9b21705ec..5b65ba4d2 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -405,6 +405,7 @@ namespace nodetool bool res = handle_command_line(vm); CHECK_AND_ASSERT_MES(res, false, "Failed to handle command line"); + m_fallback_seed_nodes_added = false; if (m_nettype == cryptonote::TESTNET) { memcpy(&m_network_id, &::config::testnet::NETWORK_ID, 16); @@ -498,6 +499,7 @@ namespace nodetool for (const auto &peer: get_seed_nodes(cryptonote::MAINNET)) full_addrs.insert(peer); + m_fallback_seed_nodes_added = true; } } } @@ -1134,7 +1136,6 @@ namespace nodetool size_t try_count = 0; size_t current_index = crypto::rand<size_t>()%m_seed_nodes.size(); - bool fallback_nodes_added = false; while(true) { if(m_net_server.is_stop_signal_sent()) @@ -1144,15 +1145,21 @@ namespace nodetool break; if(++try_count > m_seed_nodes.size()) { - if (!fallback_nodes_added) + if (!m_fallback_seed_nodes_added) { MWARNING("Failed to connect to any of seed peers, trying fallback seeds"); + current_index = m_seed_nodes.size(); for (const auto &peer: get_seed_nodes(m_nettype)) { MDEBUG("Fallback seed node: " << peer); append_net_address(m_seed_nodes, peer); } - fallback_nodes_added = true; + m_fallback_seed_nodes_added = true; + if (current_index == m_seed_nodes.size()) + { + MWARNING("No fallback seeds, continuing without seeds"); + break; + } // continue for another few cycles } else @@ -1293,6 +1300,20 @@ namespace nodetool m_connections_maker_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::connections_maker, this)); m_gray_peerlist_housekeeping_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::gray_peerlist_housekeeping, this)); m_peerlist_store_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::store_config, this)); + m_incoming_connections_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::check_incoming_connections, this)); + return true; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::check_incoming_connections() + { + if (m_offline || m_hide_my_port) + return true; + if (get_incoming_connections_count() == 0) + { + const el::Level level = el::Level::Warning; + MCLOG_RED(level, "global", "No incoming connections - check firewalls/routers allow port " << get_this_peer_port()); + } return true; } //----------------------------------------------------------------------------------- diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index b5ef2557a..dc7b6b30f 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1219,7 +1219,7 @@ namespace cryptonote return reward; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response) + bool core_rpc_server::fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash) { PERF_TIMER(fill_block_header_response); response.major_version = blk.major_version; @@ -1235,6 +1235,7 @@ namespace cryptonote response.reward = get_block_reward(blk); response.block_size = m_core.get_blockchain_storage().get_db().get_block_size(height); response.num_txes = blk.tx_hashes.size(); + response.pow_hash = fill_pow_hash ? string_tools::pod_to_hex(get_block_longhash(blk, height)) : ""; return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1324,7 +1325,7 @@ namespace cryptonote error_resp.message = "Internal error: can't get last block."; return false; } - bool response_filled = fill_block_header_response(last_block, false, last_block_height, last_block_hash, res.block_header); + bool response_filled = fill_block_header_response(last_block, false, last_block_height, last_block_hash, res.block_header, req.fill_pow_hash); if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1365,7 +1366,7 @@ namespace cryptonote return false; } uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; - bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header); + bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header, req.fill_pow_hash); if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1414,7 +1415,7 @@ namespace cryptonote return false; } res.headers.push_back(block_header_response()); - bool response_filled = fill_block_header_response(blk, false, block_height, block_hash, res.headers.back()); + bool response_filled = fill_block_header_response(blk, false, block_height, block_hash, res.headers.back(), req.fill_pow_hash); if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1447,7 +1448,7 @@ namespace cryptonote error_resp.message = "Internal error: can't get block by height. Height = " + std::to_string(req.height) + '.'; return false; } - bool response_filled = fill_block_header_response(blk, false, req.height, block_hash, res.block_header); + bool response_filled = fill_block_header_response(blk, false, req.height, block_hash, res.block_header, req.fill_pow_hash); if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1501,7 +1502,7 @@ namespace cryptonote return false; } uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; - bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header); + bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header, req.fill_pow_hash); if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -2086,6 +2087,13 @@ namespace cryptonote if (d.cached && amount == 0 && d.cached_from == req.from_height && d.cached_to == req.to_height) { res.distributions.push_back({amount, d.cached_start_height, d.cached_distribution, d.cached_base}); + if (req.cumulative) + { + auto &distribution = res.distributions.back().distribution; + distribution[0] += d.cached_base; + for (size_t n = 1; n < distribution.size(); ++n) + distribution[n] += distribution[n-1]; + } continue; } @@ -2097,6 +2105,13 @@ namespace cryptonote { res.distributions.push_back({amount, slot.start_height, slot.distribution, slot.base}); found = true; + if (req.cumulative) + { + auto &distribution = res.distributions.back().distribution; + distribution[0] += slot.base; + for (size_t n = 1; n < distribution.size(); ++n) + distribution[n] += distribution[n-1]; + } break; } } @@ -2117,12 +2132,6 @@ namespace cryptonote if (offset <= req.to_height && req.to_height - offset + 1 < distribution.size()) distribution.resize(req.to_height - offset + 1); } - if (req.cumulative) - { - distribution[0] += base; - for (size_t n = 1; n < distribution.size(); ++n) - distribution[n] += distribution[n-1]; - } if (amount == 0) { @@ -2134,6 +2143,13 @@ namespace cryptonote d.cached = true; } + if (req.cumulative) + { + distribution[0] += base; + for (size_t n = 1; n < distribution.size(); ++n) + distribution[n] += distribution[n-1]; + } + res.distributions.push_back({amount, start_height, std::move(distribution), base}); } } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 86e41e047..324f219f8 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -224,7 +224,7 @@ private: //utils uint64_t get_block_reward(const block& blk); - bool fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response); + bool fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash); enum invoke_http_mode { JON, BIN, JON_RPC }; template <typename COMMAND_TYPE> bool use_bootstrap_daemon_if_necessary(const invoke_http_mode &mode, const std::string &command_name, const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res, bool &r); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 250c88e90..1e624da1b 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,7 +49,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 1 -#define CORE_RPC_VERSION_MINOR 19 +#define CORE_RPC_VERSION_MINOR 20 #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) @@ -1165,6 +1165,7 @@ namespace cryptonote uint64_t reward; uint64_t block_size; uint64_t num_txes; + std::string pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(major_version) @@ -1180,6 +1181,7 @@ namespace cryptonote KV_SERIALIZE(reward) KV_SERIALIZE(block_size) KV_SERIALIZE(num_txes) + KV_SERIALIZE(pow_hash) END_KV_SERIALIZE_MAP() }; @@ -1187,7 +1189,10 @@ namespace cryptonote { struct request { + bool fill_pow_hash; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; @@ -1211,9 +1216,11 @@ namespace cryptonote struct request { std::string hash; + bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(hash) + KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; @@ -1237,9 +1244,11 @@ namespace cryptonote struct request { uint64_t height; + bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(height) + KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; @@ -1264,10 +1273,12 @@ namespace cryptonote { std::string hash; uint64_t height; + bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(hash) KV_SERIALIZE(height) + KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; @@ -1554,6 +1565,8 @@ namespace cryptonote std::vector<txpool_histo> histo; uint32_t num_double_spends; + txpool_stats(): bytes_total(0), bytes_min(0), bytes_max(0), bytes_med(0), fee_total(0), oldest(0), txs_total(0), num_failing(0), num_10m(0), num_not_relayed(0), histo_98pc(0), num_double_spends(0) {} + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(bytes_total) KV_SERIALIZE(bytes_min) @@ -1620,10 +1633,12 @@ namespace cryptonote { uint64_t start_height; uint64_t end_height; + bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(start_height) KV_SERIALIZE(end_height) + KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 81896ca55..196a2eb74 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -90,8 +90,6 @@ typedef cryptonote::simple_wallet sw; #define MIN_RING_SIZE 7 // Used to inform user about min ring size -- does not track actual protocol -#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" - #define LOCK_IDLE_SCOPE() \ bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \ @@ -508,7 +506,7 @@ namespace } if (warn_of_possible_attack) - fail_msg_writer() << tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a tranasction immediately. Alternatively, connect to another node so the original node cannot correlate information."); + fail_msg_writer() << tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a transaction immediately. Alternatively, connect to another node so the original node cannot correlate information."); } bool check_file_overwrite(const std::string &filename) @@ -1371,9 +1369,117 @@ bool simple_wallet::print_ring(const std::vector<std::string> &args) bool simple_wallet::set_ring(const std::vector<std::string> &args) { crypto::key_image key_image; + + // try filename first + if (args.size() == 1) + { + if (!epee::file_io_utils::is_file_exist(args[0])) + { + fail_msg_writer() << tr("File doesn't exist"); + return true; + } + + char str[4096]; + std::unique_ptr<FILE, tools::close_file> f(fopen(args[0].c_str(), "r")); + if (f) + { + while (!feof(f.get())) + { + if (!fgets(str, sizeof(str), f.get())) + break; + const size_t len = strlen(str); + if (len > 0 && str[len - 1] == '\n') + str[len - 1] = 0; + if (!str[0]) + continue; + char key_image_str[65], type_str[9]; + int read_after_key_image = 0, read = 0; + int fields = sscanf(str, "%64[abcdefABCDEF0123456789] %n%8s %n", key_image_str, &read_after_key_image, type_str, &read); + if (fields != 2) + { + fail_msg_writer() << tr("Invalid ring specification: ") << str; + continue; + } + key_image_str[64] = 0; + type_str[8] = 0; + crypto::key_image key_image; + if (read_after_key_image == 0 || !epee::string_tools::hex_to_pod(key_image_str, key_image)) + { + fail_msg_writer() << tr("Invalid key image: ") << str; + continue; + } + if (read == read_after_key_image+8 || (strcmp(type_str, "absolute") && strcmp(type_str, "relative"))) + { + fail_msg_writer() << tr("Invalid ring type, expected relative or abosolute: ") << str; + continue; + } + bool relative = !strcmp(type_str, "relative"); + if (read < 0 || (size_t)read > strlen(str)) + { + fail_msg_writer() << tr("Error reading line: ") << str; + continue; + } + bool valid = true; + std::vector<uint64_t> ring; + const char *ptr = str + read; + while (*ptr) + { + unsigned long offset; + int elements = sscanf(ptr, "%lu %n", &offset, &read); + if (elements == 0 || read <= 0 || (size_t)read > strlen(str)) + { + fail_msg_writer() << tr("Error reading line: ") << str; + valid = false; + break; + } + ring.push_back(offset); + ptr += read; + MGINFO("read offset: " << offset); + } + if (!valid) + continue; + if (ring.empty()) + { + fail_msg_writer() << tr("Invalid ring: ") << str; + continue; + } + if (relative) + { + for (size_t n = 1; n < ring.size(); ++n) + { + if (ring[n] <= 0) + { + fail_msg_writer() << tr("Invalid relative ring: ") << str; + valid = false; + break; + } + } + } + else + { + for (size_t n = 1; n < ring.size(); ++n) + { + if (ring[n] <= ring[n-1]) + { + fail_msg_writer() << tr("Invalid absolute ring: ") << str; + valid = false; + break; + } + } + } + if (!valid) + continue; + if (!m_wallet->set_ring(key_image, ring, relative)) + fail_msg_writer() << tr("Failed to set ring for key image: ") << key_image << ". " << tr("Continuing."); + } + f.reset(); + } + return true; + } + if (args.size() < 3) { - fail_msg_writer() << tr("usage: set_ring <key_image> absolute|relative <index> [<index>...]"); + fail_msg_writer() << tr("usage: set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"); return true; } @@ -2325,7 +2431,7 @@ simple_wallet::simple_wallet() tr("Print the ring(s) used to spend a given key image or transaction (if the ring size is > 1)")); m_cmd_binder.set_handler("set_ring", boost::bind(&simple_wallet::set_ring, this, _1), - tr("set_ring <key_image> absolute|relative <index> [<index>...]"), + tr("set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"), tr("Set the ring used for a given key image, so it can be reused in a fork")); m_cmd_binder.set_handler("save_known_rings", boost::bind(&simple_wallet::save_known_rings, this, _1), @@ -2451,8 +2557,24 @@ bool simple_wallet::set_log(const std::vector<std::string> &args) fail_msg_writer() << tr("usage: set_log <log_level_number_0-4> | <categories>"); return true; } - if (!args.empty()) - mlog_set_log(args[0].c_str()); + if(!args.empty()) + { + uint16_t level = 0; + if(epee::string_tools::get_xtype_from_string(level, args[0])) + { + if(4 < level) + { + fail_msg_writer() << tr("wrong number range, use: set_log <log_level_number_0-4> | <categories>"); + return true; + } + mlog_set_log_level(level); + } + else + { + mlog_set_log(args[0].c_str()); + } + } + success_msg_writer() << "New log categories: " << mlog_get_categories(); return true; } @@ -3496,6 +3618,17 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("wallet file path not valid: ") << m_wallet_file; return false; } + + bool keys_file_exists; + bool wallet_file_exists; + + tools::wallet2::wallet_exists(m_wallet_file, keys_file_exists, wallet_file_exists); + if(!keys_file_exists) + { + fail_msg_writer() << tr("Key file not found. Failed to open wallet"); + return false; + } + epee::wipeable_string password; try { @@ -3930,6 +4063,8 @@ bool simple_wallet::show_balance_unlocked(bool detailed) std::string extra; if (m_wallet->has_multisig_partial_key_images()) extra = tr(" (Some owned outputs have partial key images - import_multisig_info needed)"); + else if (m_wallet->has_unknown_key_images()) + extra += tr(" (Some owned outputs have missing key images - import_key_images needed)"); 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); @@ -6005,10 +6140,7 @@ static std::string get_human_readable_timestamp(uint64_t ts) #endif uint64_t now = time(NULL); uint64_t diff = ts > now ? ts - now : now - ts; - if (diff > 24*3600) - strftime(buffer, sizeof(buffer), "%Y-%m-%d", &tm); - else - strftime(buffer, sizeof(buffer), "%I:%M:%S %p", &tm); + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); return std::string(buffer); } //---------------------------------------------------------------------------------------------------- @@ -6120,7 +6252,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); std::string note = m_wallet->get_tx_note(pd.m_tx_hash); - output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%16.16s %20.20s %s %s %d %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str()))); + output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%25.25s %20.20s %s %s %d %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str()))); } } @@ -6153,7 +6285,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); std::string note = m_wallet->get_tx_note(i->first); - output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%16.16s %20.20s %s %s %14.14s %s %s - %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests % print_subaddr_indices(pd.m_subaddr_indices) % note).str()))); + output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%25.25s %20.20s %s %s %14.14s %s %s - %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests % print_subaddr_indices(pd.m_subaddr_indices) % note).str()))); } } @@ -6179,7 +6311,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) std::string double_spend_note; if (i->second.m_double_spend_seen) double_spend_note = tr("[Double spend seen on the network: this transaction may or may not end up being mined] "); - message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %d %s %s%s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note % double_spend_note).str(); + message_writer() << (boost::format("%8.8s %6.6s %25.25s %20.20s %s %s %d %s %s%s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note % double_spend_note).str(); } } catch (const std::exception& e) @@ -6202,7 +6334,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) std::string note = m_wallet->get_tx_note(i->first); bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed; if ((failed && is_failed) || (!is_failed && pending)) { - message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %14.14s %s - %s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % get_human_readable_timestamp(pd.m_timestamp) % print_money(amount - pd.m_change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % print_subaddr_indices(pd.m_subaddr_indices) % note).str(); + message_writer() << (boost::format("%8.8s %6.6s %25.25s %20.20s %s %s %14.14s %s - %s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % get_human_readable_timestamp(pd.m_timestamp) % print_money(amount - pd.m_change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % print_subaddr_indices(pd.m_subaddr_indices) % note).str(); } } } @@ -7171,19 +7303,8 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args) LOCK_IDLE_SCOPE(); try { - std::vector<tools::wallet2::transfer_details> outs = m_wallet->export_outputs(); - - std::stringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - ar << outs; - - std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); - const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; - std::string header; - header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); - header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); - std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str()); - bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); + std::string data = m_wallet->export_outputs_to_str(); + bool r = epee::file_io_utils::save_string_to_file(filename, data); if (!r) { fail_msg_writer() << tr("failed to save file ") << filename; @@ -7222,63 +7343,16 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args) fail_msg_writer() << tr("failed to read file ") << filename; return true; } - const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC); - if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen)) - { - fail_msg_writer() << "Bad output export file magic in " << filename; - return true; - } try { - data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen)); - } - catch (const std::exception &e) - { - fail_msg_writer() << "Failed to decrypt " << filename << ": " << e.what(); - return true; - } - - const size_t headerlen = 2 * sizeof(crypto::public_key); - if (data.size() < headerlen) - { - fail_msg_writer() << "Bad data size from file " << filename; - return true; - } - const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; - const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; - const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; - if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) - { - fail_msg_writer() << "Outputs from " << filename << " are for a different account"; - return true; - } - - try - { - std::string body(data, headerlen); - std::stringstream iss; - iss << body; - std::vector<tools::wallet2::transfer_details> outputs; - try - { - boost::archive::portable_binary_iarchive ar(iss); - ar >> outputs; - } - catch (...) - { - iss.str(""); - iss << body; - boost::archive::binary_iarchive ar(iss); - ar >> outputs; - } LOCK_IDLE_SCOPE(); - size_t n_outputs = m_wallet->import_outputs(outputs); + size_t n_outputs = m_wallet->import_outputs_from_str(data); success_msg_writer() << boost::lexical_cast<std::string>(n_outputs) << " outputs imported"; } catch (const std::exception &e) { - fail_msg_writer() << "Failed to import outputs: " << e.what(); + fail_msg_writer() << "Failed to import outputs " << filename << ": " << e.what(); return true; } @@ -7513,7 +7587,9 @@ int main(int argc, char* argv[]) po::positional_options_description positional_options; positional_options.add(arg_command.name, -1); - const auto vm = wallet_args::main( + boost::optional<po::variables_map> vm; + bool should_terminate = false; + std::tie(vm, should_terminate) = wallet_args::main( argc, argv, "monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [<COMMAND>]", sw::tr("This is the command line monero wallet. It needs to connect to a monero\ndaemon to work correctly.\nWARNING: Do not reuse your Monero keys on an another fork, UNLESS this fork has key reuse mitigations built in. Doing so will harm your privacy."), @@ -7528,6 +7604,11 @@ int main(int argc, char* argv[]) return 1; } + if (should_terminate) + { + return 0; + } + cryptonote::simple_wallet w; const bool r = w.init(*vm); CHECK_AND_ASSERT_MES(r, 1, sw::tr("Failed to initialize wallet")); diff --git a/src/version.cpp.in b/src/version.cpp.in index 9fed91d99..a03da7889 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" -#define DEF_MONERO_VERSION "0.12.1.0-master" +#define DEF_MONERO_VERSION "0.12.2.0-master" #define DEF_MONERO_RELEASE_NAME "Lithium Luna" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index ff4619f0f..8d200220d 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -34,6 +34,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "common/base58.h" #include <memory> #include <vector> @@ -102,6 +103,11 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) } // Commit tx else { + auto multisigState = m_wallet.multisig(); + if (multisigState.isMultisig && m_signers.size() < multisigState.threshold) { + throw runtime_error("Not enough signers to send multisig transaction"); + } + m_wallet.pauseRefresh(); while (!m_pending_tx.empty()) { auto & ptx = m_pending_tx.back(); @@ -188,6 +194,53 @@ std::vector<std::set<uint32_t>> PendingTransactionImpl::subaddrIndices() const return result; } +std::string PendingTransactionImpl::multisigSignData() { + try { + if (!m_wallet.multisig().isMultisig) { + throw std::runtime_error("wallet is not multisig"); + } + + auto cipher = m_wallet.m_wallet->save_multisig_tx(m_pending_tx); + return epee::string_tools::buff_to_hex_nodelimer(cipher); + } catch (const std::exception& e) { + m_status = Status_Error; + m_errorString = std::string(tr("Couldn't multisig sign data: ")) + e.what(); + } + + return std::string(); +} + +void PendingTransactionImpl::signMultisigTx() { + try { + std::vector<crypto::hash> ignore; + + tools::wallet2::multisig_tx_set txSet; + txSet.m_ptx = m_pending_tx; + txSet.m_signers = m_signers; + + if (!m_wallet.m_wallet->sign_multisig_tx(txSet, ignore)) { + throw std::runtime_error("couldn't sign multisig transaction"); + } + + std::swap(m_pending_tx, txSet.m_ptx); + std::swap(m_signers, txSet.m_signers); + } catch (const std::exception& e) { + m_status = Status_Error; + m_errorString = std::string(tr("Couldn't sign multisig transaction: ")) + e.what(); + } +} + +std::vector<std::string> PendingTransactionImpl::signersKeys() const { + std::vector<std::string> keys; + keys.reserve(m_signers.size()); + + for (const auto& signer: m_signers) { + keys.emplace_back(tools::base58::encode(cryptonote::t_serializable_object_to_blob(signer))); + } + + return keys; +} + } namespace Bitmonero = Monero; diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index d0bd66eb5..4f963c134 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -55,6 +55,10 @@ public: std::vector<std::set<uint32_t>> subaddrIndices() const; // TODO: continue with interface; + std::string multisigSignData(); + void signMultisigTx(); + std::vector<std::string> signersKeys() const; + private: friend class WalletImpl; WalletImpl &m_wallet; @@ -62,6 +66,7 @@ private: int m_status; std::string m_errorString; std::vector<tools::wallet2::pending_tx> m_pending_tx; + std::unordered_set<crypto::public_key> m_signers; }; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 367011eaa..7ade42e11 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -69,14 +69,48 @@ namespace { // Connection timeout 30 sec static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30; - std::string get_default_ringdb_path() + std::string get_default_ringdb_path(cryptonote::network_type nettype) { boost::filesystem::path dir = tools::get_default_data_dir(); // remove .bitmonero, replace with .shared-ringdb dir = dir.remove_filename(); dir /= ".shared-ringdb"; + if (nettype == cryptonote::TESTNET) + dir /= "testnet"; + else if (nettype == cryptonote::STAGENET) + dir /= "stagenet"; return dir.string(); } + + void checkMultisigWalletReady(const tools::wallet2* wallet) { + if (!wallet) { + throw runtime_error("Wallet is not initialized yet"); + } + + bool ready; + if (!wallet->multisig(&ready)) { + throw runtime_error("Wallet is not multisig"); + } + + if (!ready) { + throw runtime_error("Multisig wallet is not finalized yet"); + } + } + + void checkMultisigWalletNotReady(const tools::wallet2* wallet) { + if (!wallet) { + throw runtime_error("Wallet is not initialized yet"); + } + + bool ready; + if (!wallet->multisig(&ready)) { + throw runtime_error("Wallet is not multisig"); + } + + if (ready) { + throw runtime_error("Multisig wallet is already finalized"); + } + } } struct Wallet2CallbackImpl : public tools::i_wallet2_callback @@ -395,9 +429,9 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co // add logic to error out if new wallet requested but named wallet file exists if (keys_file_exists || wallet_file_exists) { - m_errorString = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."; - LOG_ERROR(m_errorString); - m_status = Status_Critical; + std::string error = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."; + LOG_ERROR(error); + setStatusCritical(error); return false; } // TODO: validate language @@ -406,11 +440,10 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co try { recovery_val = m_wallet->generate(path, password, secret_key, false, false); m_password = password; - m_status = Status_Ok; + clearStatus(); } catch (const std::exception &e) { LOG_ERROR("Error creating wallet: " << e.what()); - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); return false; } @@ -434,9 +467,9 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas // add logic to error out if new wallet requested but named wallet file exists if (keys_file_exists || wallet_file_exists) { - m_errorString = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting."; - LOG_ERROR(m_errorString); - m_status = Status_Error; + std::string error = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting."; + LOG_ERROR(error); + setStatusError(error); return false; } // TODO: validate language @@ -472,11 +505,10 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas uint64_t spent = 0; uint64_t unspent = 0; view_wallet->import_key_images(key_images,spent,unspent,false); - m_status = Status_Ok; + clearStatus(); } catch (const std::exception &e) { LOG_ERROR("Error creating view only wallet: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } // Store wallet @@ -503,8 +535,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, cryptonote::address_parse_info info; if(!get_account_address_from_str(info, m_wallet->nettype(), address_string)) { - m_errorString = tr("failed to parse address"); - m_status = Status_Error; + setStatusError(tr("failed to parse address")); return false; } @@ -515,8 +546,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, cryptonote::blobdata spendkey_data; if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) { - m_errorString = tr("failed to parse secret spend key"); - m_status = Status_Error; + setStatusError(tr("failed to parse secret spend key")); return false; } has_spendkey = true; @@ -524,61 +554,68 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, } // parse view secret key + bool has_viewkey = true; + crypto::secret_key viewkey; if (viewkey_string.empty()) { - m_errorString = tr("No view key supplied, cancelled"); - m_status = Status_Error; - return false; + if(has_spendkey) { + has_viewkey = false; + } + else { + setStatusError(tr("Neither view key nor spend key supplied, cancelled")); + return false; + } } - cryptonote::blobdata viewkey_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) - { - m_errorString = tr("failed to parse secret view key"); - m_status = Status_Error; - return false; + if(has_viewkey) { + cryptonote::blobdata viewkey_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) + { + setStatusError(tr("failed to parse secret view key")); + return false; + } + viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); } - crypto::secret_key viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); - // check the spend and view keys match the given address crypto::public_key pkey; if(has_spendkey) { if (!crypto::secret_key_to_public_key(spendkey, pkey)) { - m_errorString = tr("failed to verify secret spend key"); - m_status = Status_Error; + setStatusError(tr("failed to verify secret spend key")); return false; } if (info.address.m_spend_public_key != pkey) { - m_errorString = tr("spend key does not match address"); - m_status = Status_Error; + setStatusError(tr("spend key does not match address")); return false; } } - if (!crypto::secret_key_to_public_key(viewkey, pkey)) { - m_errorString = tr("failed to verify secret view key"); - m_status = Status_Error; - return false; - } - if (info.address.m_view_public_key != pkey) { - m_errorString = tr("view key does not match address"); - m_status = Status_Error; - return false; + if(has_viewkey) { + if (!crypto::secret_key_to_public_key(viewkey, pkey)) { + setStatusError(tr("failed to verify secret view key")); + return false; + } + if (info.address.m_view_public_key != pkey) { + setStatusError(tr("view key does not match address")); + return false; + } } try { - if (has_spendkey) { + if (has_spendkey && has_viewkey) { m_wallet->generate(path, password, info.address, spendkey, viewkey); - setSeedLanguage(language); - LOG_PRINT_L1("Generated new wallet from keys with seed language: " + language); + LOG_PRINT_L1("Generated new wallet from spend key and view key"); } - else { + if(!has_spendkey && has_viewkey) { m_wallet->generate(path, password, info.address, viewkey); LOG_PRINT_L1("Generated new view only wallet from keys"); } + if(has_spendkey && !has_viewkey) { + m_wallet->generate(path, password, spendkey, true, false, false); + setSeedLanguage(language); + LOG_PRINT_L1("Generated deterministic wallet from spend key with seed language: " + language); + } } catch (const std::exception& e) { - m_errorString = string(tr("failed to generate new wallet: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("failed to generate new wallet: ")) + e.what()); return false; } return true; @@ -599,16 +636,15 @@ bool WalletImpl::open(const std::string &path, const std::string &password) // Rebuilding wallet cache, using refresh height from .keys file m_rebuildWalletCache = true; } - m_wallet->set_ring_database(get_default_ringdb_path()); + m_wallet->set_ring_database(get_default_ringdb_path(m_wallet->nettype())); m_wallet->load(path, password); m_password = password; } catch (const std::exception &e) { LOG_ERROR("Error opening wallet: " << e.what()); - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } bool WalletImpl::recover(const std::string &path, const std::string &seed) @@ -621,9 +657,8 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c clearStatus(); m_errorString.clear(); if (seed.empty()) { - m_errorString = "Electrum seed is empty"; - LOG_ERROR(m_errorString); - m_status = Status_Error; + LOG_ERROR("Electrum seed is empty"); + setStatusError(tr("Electrum seed is empty")); return false; } @@ -631,8 +666,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c crypto::secret_key recovery_key; std::string old_language; if (!crypto::ElectrumWords::words_to_bytes(seed, recovery_key, old_language)) { - m_errorString = "Electrum-style word list failed verification"; - m_status = Status_Error; + setStatusError(tr("Electrum-style word list failed verification")); return false; } @@ -644,10 +678,9 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c m_wallet->generate(path, password, recovery_key, true, false); } catch (const std::exception &e) { - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } bool WalletImpl::close(bool store) @@ -671,8 +704,7 @@ bool WalletImpl::close(bool store) result = true; clearStatus(); } catch (const std::exception &e) { - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); LOG_ERROR("Error closing wallet: " << e.what()); } return result; @@ -698,14 +730,22 @@ void WalletImpl::setSeedLanguage(const std::string &arg) int WalletImpl::status() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); return m_status; } std::string WalletImpl::errorString() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); return m_errorString; } +void WalletImpl::statusWithErrorString(int& status, std::string& errorString) const { + boost::lock_guard<boost::mutex> l(m_statusMutex); + status = m_status; + errorString = m_errorString; +} + bool WalletImpl::setPassword(const std::string &password) { clearStatus(); @@ -713,10 +753,9 @@ bool WalletImpl::setPassword(const std::string &password) m_wallet->rewrite(m_wallet->get_wallet_file(), password); m_password = password; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } std::string WalletImpl::address(uint32_t accountIndex, uint32_t addressIndex) const @@ -753,6 +792,16 @@ std::string WalletImpl::publicSpendKey() const return epee::string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key); } +std::string WalletImpl::publicMultisigSignerKey() const +{ + try { + crypto::public_key signer = m_wallet->get_multisig_signer_public_key(); + return epee::string_tools::pod_to_hex(signer); + } catch (const std::exception&) { + return ""; + } +} + std::string WalletImpl::path() const { return m_wallet->path(); @@ -769,11 +818,11 @@ bool WalletImpl::store(const std::string &path) } } catch (const std::exception &e) { LOG_ERROR("Error saving wallet: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); + return false; } - return m_status == Status_Ok; + return true; } string WalletImpl::filename() const @@ -806,8 +855,7 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ { cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response response; if(!m_wallet->light_wallet_import_wallet_request(response)){ - m_errorString = tr("Failed to send import wallet request"); - m_status = Status_Error; + setStatusError(tr("Failed to send import wallet request")); return false; } fee = response.import_fee; @@ -820,8 +868,7 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ catch (const std::exception &e) { LOG_ERROR("Error sending import wallet request: " << e.what()); - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); return false; } return true; @@ -870,12 +917,9 @@ uint64_t WalletImpl::daemonBlockChainHeight() const if (!err.empty()) { LOG_ERROR(__FUNCTION__ << ": " << err); result = 0; - m_errorString = err; - m_status = Status_Error; - + setStatusError(err); } else { - m_status = Status_Ok; - m_errorString = ""; + clearStatus(); } return result; } @@ -892,12 +936,9 @@ uint64_t WalletImpl::daemonBlockChainTargetHeight() const if (!err.empty()) { LOG_ERROR(__FUNCTION__ << ": " << err); result = 0; - m_errorString = err; - m_status = Status_Error; - + setStatusError(err); } else { - m_status = Status_Ok; - m_errorString = ""; + clearStatus(); } // Target height can be 0 when daemon is synced. Use blockchain height instead. if(result == 0) @@ -921,8 +962,10 @@ bool WalletImpl::synchronized() const bool WalletImpl::refresh() { clearStatus(); + //TODO: make doRefresh return bool to know whether the error occured during refresh or not + //otherwise one may try, say, to send transaction, transfer fails and this method returns false doRefresh(); - return m_status == Status_Ok; + return status() == Status_Ok; } void WalletImpl::refreshAsync() @@ -952,8 +995,7 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file clearStatus(); UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ - m_errorString = tr("Failed to load unsigned transactions"); - m_status = Status_Error; + setStatusError(tr("Failed to load unsigned transactions")); } // Check tx data and construct confirmation message @@ -961,8 +1003,7 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file if (!transaction->m_unsigned_tx_set.transfers.empty()) extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.size()).str(); transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); - m_status = transaction->status(); - m_errorString = transaction->errorString(); + setStatus(transaction->status(), transaction->errorString()); return transaction; } @@ -973,14 +1014,12 @@ bool WalletImpl::submitTransaction(const string &fileName) { bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx); if (!r) { - m_errorString = tr("Failed to load transaction from file"); - m_status = Status_Ok; + setStatus(Status_Ok, tr("Failed to load transaction from file")); return false; } if(!transaction->commit()) { - m_errorString = transaction->m_errorString; - m_status = Status_Error; + setStatusError(transaction->m_errorString); return false; } @@ -991,8 +1030,7 @@ bool WalletImpl::exportKeyImages(const string &filename) { if (m_wallet->watch_only()) { - m_errorString = tr("Wallet is view only"); - m_status = Status_Error; + setStatusError(tr("Wallet is view only")); return false; } @@ -1000,16 +1038,14 @@ bool WalletImpl::exportKeyImages(const string &filename) { if (!m_wallet->export_key_images(filename)) { - m_errorString = tr("failed to save file ") + filename; - m_status = Status_Error; + setStatusError(tr("failed to save file ") + filename); return false; } } catch (const std::exception &e) { LOG_ERROR("Error exporting key images: " << e.what()); - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); return false; } return true; @@ -1018,8 +1054,7 @@ bool WalletImpl::exportKeyImages(const string &filename) bool WalletImpl::importKeyImages(const string &filename) { if (!trustedDaemon()) { - m_status = Status_Error; - m_errorString = tr("Key images can only be imported with a trusted daemon"); + setStatusError(tr("Key images can only be imported with a trusted daemon")); return false; } try @@ -1032,8 +1067,7 @@ bool WalletImpl::importKeyImages(const string &filename) catch (const std::exception &e) { LOG_ERROR("Error exporting key images: " << e.what()); - m_errorString = string(tr("Failed to import key images: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to import key images: ")) + e.what()); return false; } @@ -1065,8 +1099,7 @@ std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addre catch (const std::exception &e) { LOG_ERROR("Error getting subaddress label: ") << e.what(); - m_errorString = string(tr("Failed to get subaddress label: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to get subaddress label: ")) + e.what()); return ""; } } @@ -1079,9 +1112,134 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex catch (const std::exception &e) { LOG_ERROR("Error setting subaddress label: ") << e.what(); - m_errorString = string(tr("Failed to set subaddress label: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to set subaddress label: ")) + e.what()); + } +} + +MultisigState WalletImpl::multisig() const { + MultisigState state; + state.isMultisig = m_wallet->multisig(&state.isReady, &state.threshold, &state.total); + + return state; +} + +string WalletImpl::getMultisigInfo() const { + try { + clearStatus(); + return m_wallet->get_multisig_info(); + } catch (const exception& e) { + LOG_ERROR("Error on generating multisig info: ") << e.what(); + setStatusError(string(tr("Failed to get multisig info: ")) + e.what()); + } + + return string(); +} + +string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold) { + try { + clearStatus(); + + if (m_wallet->multisig()) { + throw runtime_error("Wallet is already multisig"); + } + + return m_wallet->make_multisig(epee::wipeable_string(m_password), info, threshold); + } catch (const exception& e) { + LOG_ERROR("Error on making multisig wallet: ") << e.what(); + setStatusError(string(tr("Failed to make multisig: ")) + e.what()); + } + + return string(); +} + +bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) { + try { + clearStatus(); + checkMultisigWalletNotReady(m_wallet); + + if (m_wallet->finalize_multisig(epee::wipeable_string(m_password), extraMultisigInfo)) { + return true; + } + + setStatusError(tr("Failed to finalize multisig wallet creation")); + } catch (const exception& e) { + LOG_ERROR("Error on finalizing multisig wallet creation: ") << e.what(); + setStatusError(string(tr("Failed to finalize multisig wallet creation: ")) + e.what()); + } + + return false; +} + +bool WalletImpl::exportMultisigImages(string& images) { + try { + clearStatus(); + checkMultisigWalletReady(m_wallet); + + auto blob = m_wallet->export_multisig(); + images = epee::string_tools::buff_to_hex_nodelimer(blob); + return true; + } catch (const exception& e) { + LOG_ERROR("Error on exporting multisig images: ") << e.what(); + setStatusError(string(tr("Failed to export multisig images: ")) + e.what()); + } + + return false; +} + +size_t WalletImpl::importMultisigImages(const vector<string>& images) { + try { + clearStatus(); + checkMultisigWalletReady(m_wallet); + + std::vector<std::string> blobs; + blobs.reserve(images.size()); + + for (const auto& image: images) { + std::string blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(image, blob)) { + LOG_ERROR("Failed to parse imported multisig images"); + setStatusError(tr("Failed to parse imported multisig images")); + return 0; + } + + blobs.emplace_back(std::move(blob)); + } + + return m_wallet->import_multisig(blobs); + } catch (const exception& e) { + LOG_ERROR("Error on importing multisig images: ") << e.what(); + setStatusError(string(tr("Failed to import multisig images: ")) + e.what()); + } + + return 0; +} + +PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signData) { + try { + clearStatus(); + checkMultisigWalletReady(m_wallet); + + string binary; + if (!epee::string_tools::parse_hexstr_to_binbuff(signData, binary)) { + throw runtime_error("Failed to deserialize multisig transaction"); + } + + tools::wallet2::multisig_tx_set txSet; + if (!m_wallet->load_multisig_tx(binary, txSet, {})) { + throw runtime_error("couldn't parse multisig transaction data"); + } + + auto ptx = new PendingTransactionImpl(*this); + ptx->m_pending_tx = txSet.m_ptx; + ptx->m_signers = txSet.m_signers; + + return ptx; + } catch (exception& e) { + LOG_ERROR("Error on restoring multisig transaction: ") << e.what(); + setStatusError(string(tr("Failed to restore multisig transaction: ")) + e.what()); } + + return nullptr; } // TODO: @@ -1117,8 +1275,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const do { if(!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), dst_addr)) { // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 - m_status = Status_Error; - m_errorString = "Invalid destination address"; + setStatusError(tr("Invalid destination address")); break; } @@ -1143,8 +1300,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const } if (!r) { - m_status = Status_Error; - m_errorString = tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id; + setStatusError(tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id); break; } } @@ -1153,8 +1309,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); if (!r) { - m_status = Status_Error; - m_errorString = tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id); + setStatusError(tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id)); break; } } @@ -1185,40 +1340,33 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const extra, subaddr_account, subaddr_indices, m_trustedDaemon); } + if (multisig().isMultisig) { + transaction->m_signers = m_wallet->make_multisig_tx_set(transaction->m_pending_tx).m_signers; + } } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? - m_errorString = tr("daemon is busy. Please try again later."); - m_status = Status_Error; + setStatusError(tr("daemon is busy. Please try again later.")); } catch (const tools::error::no_connection_to_daemon&) { - m_errorString = tr("no connection to daemon. Please make sure daemon is running."); - m_status = Status_Error; + setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); } catch (const tools::error::wallet_rpc_error& e) { - m_errorString = tr("RPC error: ") + e.to_string(); - m_status = Status_Error; + setStatusError(tr("RPC error: ") + e.to_string()); } catch (const tools::error::get_random_outs_error &e) { - m_errorString = (boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str(); - m_status = Status_Error; - + setStatusError((boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str()); } catch (const tools::error::not_enough_unlocked_money& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_money& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::tx_not_possible& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % @@ -1226,8 +1374,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_outs_to_mix& e) { std::ostringstream writer; writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; @@ -1235,42 +1382,31 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; } writer << "\n" << tr("Please sweep unmixable outputs."); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_not_constructed&) { - m_errorString = tr("transaction was not constructed"); - m_status = Status_Error; + setStatusError(tr("transaction was not constructed")); } catch (const tools::error::tx_rejected& e) { std::ostringstream writer; writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_sum_overflow& e) { - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); } catch (const tools::error::zero_destination&) { - m_errorString = tr("one of destinations is zero"); - m_status = Status_Error; + setStatusError(tr("one of destinations is zero")); } catch (const tools::error::tx_too_big& e) { - m_errorString = tr("failed to find a suitable way to split transactions"); - m_status = Status_Error; + setStatusError(tr("failed to find a suitable way to split transactions")); } catch (const tools::error::transfer_error& e) { - m_errorString = string(tr("unknown transfer error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unknown transfer error: ")) + e.what()); } catch (const tools::error::wallet_internal_error& e) { - m_errorString = string(tr("internal error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("internal error: ")) + e.what()); } catch (const std::exception& e) { - m_errorString = string(tr("unexpected error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unexpected error: ")) + e.what()); } catch (...) { - m_errorString = tr("unknown error"); - m_status = Status_Error; + setStatusError(tr("unknown error")); } } while (false); - transaction->m_status = m_status; - transaction->m_errorString = m_errorString; + statusWithErrorString(transaction->m_status, transaction->m_errorString); // Resume refresh thread startRefresh(); return transaction; @@ -1291,38 +1427,31 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? - m_errorString = tr("daemon is busy. Please try again later."); - m_status = Status_Error; + setStatusError(tr("daemon is busy. Please try again later.")); } catch (const tools::error::no_connection_to_daemon&) { - m_errorString = tr("no connection to daemon. Please make sure daemon is running."); - m_status = Status_Error; + setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); } catch (const tools::error::wallet_rpc_error& e) { - m_errorString = tr("RPC error: ") + e.to_string(); - m_status = Status_Error; + setStatusError(tr("RPC error: ") + e.to_string()); } catch (const tools::error::get_random_outs_error&) { - m_errorString = tr("failed to get random outputs to mix"); - m_status = Status_Error; - + setStatusError(tr("failed to get random outputs to mix")); } catch (const tools::error::not_enough_unlocked_money& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_money& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::tx_not_possible& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % @@ -1330,50 +1459,38 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_outs_to_mix& e) { std::ostringstream writer; writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; for (const std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) { writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; } - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_not_constructed&) { - m_errorString = tr("transaction was not constructed"); - m_status = Status_Error; + setStatusError(tr("transaction was not constructed")); } catch (const tools::error::tx_rejected& e) { std::ostringstream writer; writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_sum_overflow& e) { - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); } catch (const tools::error::zero_destination&) { - m_errorString = tr("one of destinations is zero"); - m_status = Status_Error; + setStatusError(tr("one of destinations is zero")); } catch (const tools::error::tx_too_big& e) { - m_errorString = tr("failed to find a suitable way to split transactions"); - m_status = Status_Error; + setStatusError(tr("failed to find a suitable way to split transactions")); } catch (const tools::error::transfer_error& e) { - m_errorString = string(tr("unknown transfer error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unknown transfer error: ")) + e.what()); } catch (const tools::error::wallet_internal_error& e) { - m_errorString = string(tr("internal error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("internal error: ")) + e.what()); } catch (const std::exception& e) { - m_errorString = string(tr("unexpected error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unexpected error: ")) + e.what()); } catch (...) { - m_errorString = tr("unknown error"); - m_status = Status_Error; + setStatusError(tr("unknown error")); } } while (false); - transaction->m_status = m_status; - transaction->m_errorString = m_errorString; + statusWithErrorString(transaction->m_status, transaction->m_errorString); return transaction; } @@ -1444,8 +1561,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } @@ -1453,7 +1569,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const std::vector<crypto::secret_key> additional_tx_keys; if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - m_status = Status_Ok; + clearStatus(); std::ostringstream oss; oss << epee::string_tools::pod_to_hex(tx_key); for (size_t i = 0; i < additional_tx_keys.size(); ++i) @@ -1462,8 +1578,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const } else { - m_status = Status_Error; - m_errorString = tr("no tx keys found for this txid"); + setStatusError(tr("no tx keys found for this txid")); return ""; } } @@ -1473,8 +1588,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } @@ -1482,8 +1596,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, std::vector<crypto::secret_key> additional_tx_keys; if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), tx_key)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse tx key"); + setStatusError(tr("Failed to parse tx key")); return false; } tx_key_str = tx_key_str.substr(64); @@ -1492,8 +1605,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, additional_tx_keys.resize(additional_tx_keys.size() + 1); if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), additional_tx_keys.back())) { - m_status = Status_Error; - m_errorString = tr("Failed to parse tx key"); + setStatusError(tr("Failed to parse tx key")); return false; } tx_key_str = tx_key_str.substr(64); @@ -1502,21 +1614,19 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } try { m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, received, in_pool, confirmations); - m_status = Status_Ok; + clearStatus(); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1526,28 +1636,25 @@ std::string WalletImpl::getTxProof(const std::string &txid_str, const std::strin crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return ""; } try { - m_status = Status_Ok; + clearStatus(); return m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, message); } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1557,29 +1664,26 @@ bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &ad crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } try { good = m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, message, signature, received, in_pool, confirmations); - m_status = Status_Ok; + clearStatus(); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1588,20 +1692,18 @@ std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::st crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } try { - m_status = Status_Ok; + clearStatus(); return m_wallet->get_spend_proof(txid, message); } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1611,21 +1713,19 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } try { - m_status = Status_Ok; + clearStatus(); good = m_wallet->check_spend_proof(txid, message, signature); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1633,7 +1733,7 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const { try { - m_status = Status_Ok; + clearStatus(); boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; if (!all) { @@ -1643,8 +1743,7 @@ std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64 } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1653,28 +1752,25 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } if (info.is_subaddress) { - m_status = Status_Error; - m_errorString = tr("Address must not be a subaddress"); + setStatusError(tr("Address must not be a subaddress")); return false; } good = false; try { - m_status = Status_Ok; + clearStatus(); good = m_wallet->check_reserve_proof(info.address, message, signature, total, spent); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1694,13 +1790,57 @@ bool WalletImpl::verifySignedMessage(const std::string &message, const std::stri return m_wallet->verify(message, info.address, signature); } +std::string WalletImpl::signMultisigParticipant(const std::string &message) const +{ + clearStatus(); + + bool ready = false; + if (!m_wallet->multisig(&ready) || !ready) { + m_status = Status_Error; + m_errorString = tr("The wallet must be in multisig ready state"); + return {}; + } + + try { + return m_wallet->sign_multisig_participant(message); + } catch (const std::exception& e) { + m_status = Status_Error; + m_errorString = e.what(); + } + + return {}; +} + +bool WalletImpl::verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const +{ + clearStatus(); + + cryptonote::blobdata pkeyData; + if(!epee::string_tools::parse_hexstr_to_binbuff(publicKey, pkeyData) || pkeyData.size() != sizeof(crypto::public_key)) + { + m_status = Status_Error; + m_errorString = tr("Given string is not a key"); + return false; + } + + try { + crypto::public_key pkey = *reinterpret_cast<const crypto::public_key*>(pkeyData.data()); + return m_wallet->verify_with_public_key(message, pkey, signature); + } catch (const std::exception& e) { + m_status = Status_Error; + m_errorString = e.what(); + } + + return false; +} + bool WalletImpl::connectToDaemon() { bool result = m_wallet->check_connection(NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); - m_status = result ? Status_Ok : Status_Error; if (!result) { - m_errorString = "Error connecting to daemon at " + m_wallet->get_daemon_address(); + setStatusError("Error connecting to daemon at " + m_wallet->get_daemon_address()); } else { + clearStatus(); // start refreshing here } return result; @@ -1735,10 +1875,28 @@ bool WalletImpl::watchOnly() const void WalletImpl::clearStatus() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); m_status = Status_Ok; m_errorString.clear(); } +void WalletImpl::setStatusError(const std::string& message) const +{ + setStatus(Status_Error, message); +} + +void WalletImpl::setStatusCritical(const std::string& message) const +{ + setStatus(Status_Critical, message); +} + +void WalletImpl::setStatus(int status, const std::string& message) const +{ + boost::lock_guard<boost::mutex> l(m_statusMutex); + m_status = status; + m_errorString = message; +} + void WalletImpl::refreshThreadFunc() { LOG_PRINT_L3(__FUNCTION__ << ": starting refresh thread"); @@ -1752,7 +1910,7 @@ void WalletImpl::refreshThreadFunc() // if auto refresh enabled, we wait for the "m_refreshIntervalSeconds" interval. // if not - we wait forever if (m_refreshIntervalMillis > 0) { - boost::posix_time::milliseconds wait_for_ms(m_refreshIntervalMillis); + boost::posix_time::milliseconds wait_for_ms(m_refreshIntervalMillis.load()); m_refreshCV.timed_wait(lock, wait_for_ms); } else { m_refreshCV.wait(lock); @@ -1760,7 +1918,7 @@ void WalletImpl::refreshThreadFunc() LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired..."); LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled); - LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << m_status); + LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << status()); if (m_refreshEnabled) { LOG_PRINT_L3(__FUNCTION__ << ": refreshing..."); doRefresh(); @@ -1792,8 +1950,7 @@ void WalletImpl::doRefresh() LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced"); } } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); } if (m_wallet2Callback->getListener()) { m_wallet2Callback->getListener()->refreshed(); @@ -1882,16 +2039,14 @@ bool WalletImpl::rescanSpent() { clearStatus(); if (!trustedDaemon()) { - m_status = Status_Error; - m_errorString = tr("Rescan spent can only be used with a trusted daemon"); + setStatusError(tr("Rescan spent can only be used with a trusted daemon")); return false; } try { m_wallet->rescan_spent(); } catch (const std::exception &e) { LOG_ERROR(__FUNCTION__ << " error: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } return true; @@ -1917,8 +2072,7 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool crypto::public_key pkey; if (!epee::string_tools::hex_to_pod(str, pkey)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse output public key"); + setStatusError(tr("Failed to parse output public key")); return false; } raw_pubkeys.push_back(pkey); @@ -1926,8 +2080,7 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool bool ret = m_wallet->set_blackballed_outputs(raw_pubkeys, add); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to set blackballed outputs"); + setStatusError(tr("Failed to set blackballed outputs")); return false; } return true; @@ -1938,15 +2091,13 @@ bool WalletImpl::unblackballOutput(const std::string &pubkey) crypto::public_key raw_pubkey; if (!epee::string_tools::hex_to_pod(pubkey, raw_pubkey)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse output public key"); + setStatusError(tr("Failed to parse output public key")); return false; } bool ret = m_wallet->unblackball_output(raw_pubkey); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to unblackball output"); + setStatusError(tr("Failed to unblackball output")); return false; } return true; @@ -1957,15 +2108,13 @@ bool WalletImpl::getRing(const std::string &key_image, std::vector<uint64_t> &ri crypto::key_image raw_key_image; if (!epee::string_tools::hex_to_pod(key_image, raw_key_image)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse key image"); + setStatusError(tr("Failed to parse key image")); return false; } bool ret = m_wallet->get_ring(raw_key_image, ring); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to get ring"); + setStatusError(tr("Failed to get ring")); return false; } return true; @@ -1976,16 +2125,14 @@ bool WalletImpl::getRings(const std::string &txid, std::vector<std::pair<std::st crypto::hash raw_txid; if (!epee::string_tools::hex_to_pod(txid, raw_txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> raw_rings; bool ret = m_wallet->get_rings(raw_txid, raw_rings); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to get rings"); + setStatusError(tr("Failed to get rings")); return false; } for (const auto &r: raw_rings) @@ -2000,15 +2147,13 @@ bool WalletImpl::setRing(const std::string &key_image, const std::vector<uint64_ crypto::key_image raw_key_image; if (!epee::string_tools::hex_to_pod(key_image, raw_key_image)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse key image"); + setStatusError(tr("Failed to parse key image")); return false; } bool ret = m_wallet->set_ring(raw_key_image, ring, relative); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to set ring"); + setStatusError(tr("Failed to set ring")); return false; } return true; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 4929c9673..813ca4b30 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -83,6 +83,7 @@ public: // void setListener(Listener *) {} int status() const; std::string errorString() const; + void statusWithErrorString(int& status, std::string& errorString) const override; bool setPassword(const std::string &password); std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const; std::string integratedAddress(const std::string &payment_id) const; @@ -90,6 +91,7 @@ public: std::string publicViewKey() const; std::string secretSpendKey() const; std::string publicSpendKey() const; + std::string publicMultisigSignerKey() const; std::string path() const; bool store(const std::string &path); std::string filename() const; @@ -126,6 +128,14 @@ public: std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const; void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label); + MultisigState multisig() const override; + std::string getMultisigInfo() const override; + std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override; + bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override; + bool exportMultisigImages(std::string& images) override; + size_t importMultisigImages(const std::vector<std::string>& images) override; + PendingTransaction* restoreMultisigTransaction(const std::string& signData) override; + PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, optional<uint64_t> amount, uint32_t mixin_count, PendingTransaction::Priority priority = PendingTransaction::Priority_Low, @@ -157,6 +167,8 @@ public: virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const; virtual std::string signMessage(const std::string &message); virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const; + virtual std::string signMultisigParticipant(const std::string &message) const; + virtual bool verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const; virtual void startRefresh(); virtual void pauseRefresh(); virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error); @@ -174,6 +186,9 @@ public: private: void clearStatus() const; + void setStatusError(const std::string& message) const; + void setStatusCritical(const std::string& message) const; + void setStatus(int status, const std::string& message) const; void refreshThreadFunc(); void doRefresh(); bool daemonSynced() const; @@ -191,7 +206,8 @@ private: friend class SubaddressAccountImpl; tools::wallet2 * m_wallet; - mutable std::atomic<int> m_status; + mutable boost::mutex m_statusMutex; + mutable int m_status; mutable std::string m_errorString; std::string m_password; TransactionHistoryImpl * m_history; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 4fbc7298a..5b99bd975 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -100,6 +100,30 @@ struct PendingTransaction virtual uint64_t txCount() const = 0; virtual std::vector<uint32_t> subaddrAccount() const = 0; virtual std::vector<std::set<uint32_t>> subaddrIndices() const = 0; + + /** + * @brief multisigSignData + * @return encoded multisig transaction with signers' keys. + * Transfer this data to another wallet participant to sign it. + * Assumed use case is: + * 1. Initiator: + * auto data = pendingTransaction->multisigSignData(); + * 2. Signer1: + * pendingTransaction = wallet->restoreMultisigTransaction(data); + * pendingTransaction->signMultisigTx(); + * auto signed = pendingTransaction->multisigSignData(); + * 3. Signer2: + * pendingTransaction = wallet->restoreMultisigTransaction(signed); + * pendingTransaction->signMultisigTx(); + * pendingTransaction->commit(); + */ + virtual std::string multisigSignData() = 0; + virtual void signMultisigTx() = 0; + /** + * @brief signersKeys + * @return vector of base58-encoded signers' public keys + */ + virtual std::vector<std::string> signersKeys() const = 0; }; /** @@ -291,6 +315,15 @@ struct SubaddressAccount virtual void refresh() = 0; }; +struct MultisigState { + MultisigState() : isMultisig(false), isReady(false), threshold(0), total(0) {} + + bool isMultisig; + bool isReady; + uint32_t threshold; + uint32_t total; +}; + struct WalletListener { virtual ~WalletListener() = 0; @@ -358,9 +391,11 @@ struct Wallet virtual std::string getSeedLanguage() const = 0; virtual void setSeedLanguage(const std::string &arg) = 0; //! returns wallet status (Status_Ok | Status_Error) - virtual int status() const = 0; + virtual int status() const = 0; //deprecated: use safe alternative statusWithErrorString //! in case error status, returns error string - virtual std::string errorString() const = 0; + virtual std::string errorString() const = 0; //deprecated: use safe alternative statusWithErrorString + //! 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 std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const = 0; std::string mainAddress() const { return address(0, 0); } @@ -409,6 +444,12 @@ struct Wallet virtual std::string publicSpendKey() const = 0; /*! + * \brief publicMultisigSignerKey - returns public signer key + * \return - public multisignature signer key or empty string if wallet is not multisig + */ + virtual std::string publicMultisigSignerKey() const = 0; + + /*! * \brief store - stores wallet to file. * \param path - main filename to store wallet to. additionally stores address file and keys file. * to store to the same file - just pass empty string; @@ -629,6 +670,48 @@ struct Wallet */ virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0; + /** + * @brief multisig - returns current state of multisig wallet creation process + * @return MultisigState struct + */ + virtual MultisigState multisig() const = 0; + /** + * @brief getMultisigInfo + * @return serialized and signed multisig info string + */ + virtual std::string getMultisigInfo() const = 0; + /** + * @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets + * @param info - vector of multisig infos from other participants obtained with getMulitisInfo call + * @param threshold - number of required signers to make valid transaction. Must be equal to number of participants (N) or N - 1 + * @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info + */ + virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0; + /** + * @brief finalizeMultisig - finalizes N - 1 / N multisig wallets creation + * @param extraMultisigInfo - wallet participants' extra multisig info obtained with makeMultisig call + * @return true if success + */ + virtual bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) = 0; + /** + * @brief exportMultisigImages - exports transfers' key images + * @param images - output paramter for hex encoded array of images + * @return true if success + */ + virtual bool exportMultisigImages(std::string& images) = 0; + /** + * @brief importMultisigImages - imports other participants' multisig images + * @param images - array of hex encoded arrays of images obtained with exportMultisigImages + * @return number of imported images + */ + virtual size_t importMultisigImages(const std::vector<std::string>& images) = 0; + + /** + * @brief restoreMultisigTransaction creates PendingTransaction from signData + * @param signData encrypted unsigned transaction. Obtained with PendingTransaction::multisigSignData + * @return PendingTransaction + */ + virtual PendingTransaction* restoreMultisigTransaction(const std::string& signData) = 0; /*! * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored * \param dst_addr destination address as string @@ -748,6 +831,21 @@ struct Wallet */ virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; + /*! + * \brief signMultisigParticipant signs given message with the multisig public signer key + * \param message message to sign + * \return signature in case of success. Sets status to Error and return empty string in case of error + */ + virtual std::string signMultisigParticipant(const std::string &message) const = 0; + /*! + * \brief verifyMessageWithPublicKey verifies that message was signed with the given public key + * \param message message + * \param publicKey hex encoded public key + * \param signature signature of the message + * \return true if the signature is correct. false and sets error state in case of error + */ + virtual bool verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const = 0; + virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0; virtual std::string getDefaultDataDir() const = 0; diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 44992520f..3f2634c8b 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -190,7 +190,8 @@ namespace tools { ringdb::ringdb(std::string filename, const std::string &genesis): - filename(filename) + filename(filename), + env(NULL) { MDB_txn *txn; bool tx_active = false; @@ -227,9 +228,18 @@ ringdb::ringdb(std::string filename, const std::string &genesis): ringdb::~ringdb() { - mdb_dbi_close(env, dbi_rings); - mdb_dbi_close(env, dbi_blackballs); - mdb_env_close(env); + close(); +} + +void ringdb::close() +{ + if (env) + { + mdb_dbi_close(env, dbi_rings); + mdb_dbi_close(env, dbi_blackballs); + mdb_env_close(env); + env = NULL; + } } bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 2bd1ac149..6b4bce124 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -41,6 +41,7 @@ namespace tools { public: ringdb(std::string filename, const std::string &genesis); + void close(); ~ringdb(); bool add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d11c99378..bb0953689 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -110,11 +110,15 @@ using namespace cryptonote; #define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" +#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" + #define SEGREGATION_FORK_HEIGHT 1546000 #define TESTNET_SEGREGATION_FORK_HEIGHT 1000000 #define STAGENET_SEGREGATION_FORK_HEIGHT 1000000 #define SEGREGATION_FORK_VICINITY 1500 /* blocks */ +static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; + namespace { @@ -141,13 +145,15 @@ struct options { 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<bool> restricted = {"restricted-rpc", tools::wallet2::tr("Restricts to view-only commands"), false}; - const command_line::arg_descriptor<std::string, false, true> shared_ringdb_dir = { + const command_line::arg_descriptor<std::string, false, true, 2> shared_ringdb_dir = { "shared-ringdb-dir", tools::wallet2::tr("Set shared ring database path"), get_default_ringdb_path(), - testnet, - [](bool testnet, bool defaulted, std::string val)->std::string { - if (testnet) + {{ &testnet, &stagenet }}, + [](std::array<bool, 2> testnet_stagenet, bool defaulted, std::string val)->std::string { + if (testnet_stagenet[0]) return (boost::filesystem::path(val) / "testnet").string(); + else if (testnet_stagenet[1]) + return (boost::filesystem::path(val) / "stagenet").string(); return val; } }; @@ -3618,6 +3624,14 @@ bool wallet2::has_multisig_partial_key_images() const return false; } +bool wallet2::has_unknown_key_images() const +{ + for (const auto &td: m_transfers) + if (!td.m_key_image_known) + return true; + return false; +} + /*! * \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there) * \param wallet_name Name of wallet file (should exist) @@ -3780,7 +3794,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass { wallet2::cache_file_data cache_file_data; std::string buf; - bool r = epee::file_io_utils::load_file_to_string(m_wallet_file, buf); + bool r = epee::file_io_utils::load_file_to_string(m_wallet_file, buf, std::numeric_limits<size_t>::max()); THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file); // try to read it as an encrypted cache @@ -4661,6 +4675,15 @@ void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector) bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) const { LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions"); + std::string ciphertext = dump_tx_to_str(ptx_vector); + if (ciphertext.empty()) + return false; + return epee::file_io_utils::save_string_to_file(filename, ciphertext); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) const +{ + LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions"); unsigned_tx_set txs; for (auto &tx: ptx_vector) { @@ -4680,11 +4703,11 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri } catch (...) { - return false; + return std::string(); } LOG_PRINT_L2("Saving unsigned tx data: " << oss.str()); std::string ciphertext = encrypt_with_view_secret_key(oss.str()); - return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + ciphertext); + return std::string(UNSIGNED_TX_PREFIX) + ciphertext; } //---------------------------------------------------------------------------------------------------- bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) const @@ -4702,10 +4725,17 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx LOG_PRINT_L0("Failed to load from " << unsigned_filename); return false; } + + return parse_unsigned_tx_from_str(s, exported_txs); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const +{ + std::string s = unsigned_tx_st; const size_t magiclen = strlen(UNSIGNED_TX_PREFIX) - 1; if (strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen)) { - LOG_PRINT_L0("Bad magic from " << unsigned_filename); + LOG_PRINT_L0("Bad magic from unsigned tx"); return false; } s = s.substr(magiclen); @@ -4721,7 +4751,7 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx } catch (...) { - LOG_PRINT_L0("Failed to parse data from " << unsigned_filename); + LOG_PRINT_L0("Failed to parse data from unsigned tx"); return false; } } @@ -4738,19 +4768,19 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx } catch (...) { - LOG_PRINT_L0("Failed to parse data from " << unsigned_filename); + LOG_PRINT_L0("Failed to parse data from unsigned tx"); return false; } } catch (const std::exception &e) { - LOG_PRINT_L0("Failed to decrypt " << unsigned_filename << ": " << e.what()); + LOG_PRINT_L0("Failed to decrypt unsigned tx: " << e.what()); return false; } } else { - LOG_PRINT_L0("Unsupported version in " << unsigned_filename); + LOG_PRINT_L0("Unsupported version in unsigned tx"); return false; } LOG_PRINT_L1("Loaded tx unsigned data from binary: " << exported_txs.txes.size() << " transactions"); @@ -4771,14 +4801,12 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s } return sign_tx(exported_txs, signed_filename, txs, export_raw); } - //---------------------------------------------------------------------------------------------------- -bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, bool export_raw) +bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &txs, signed_tx_set &signed_txes) { import_outputs(exported_txs.transfers); // sign the transactions - signed_tx_set signed_txes; for (size_t n = 0; n < exported_txs.txes.size(); ++n) { tools::wallet2::tx_construction_data &sd = exported_txs.txes[n]; @@ -4840,19 +4868,20 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f signed_txes.key_images[i] = m_transfers[i].m_key_image; } - // save as binary - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << signed_txes; - } - catch(...) + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, bool export_raw) +{ + // sign the transactions + signed_tx_set signed_txes; + std::string ciphertext = sign_tx_dump_to_str(exported_txs, txs, signed_txes); + if (ciphertext.empty()) { + LOG_PRINT_L0("Failed to sign unsigned_tx_set"); return false; } - LOG_PRINT_L3("Saving signed tx data (with encryption): " << oss.str()); - std::string ciphertext = encrypt_with_view_secret_key(oss.str()); + if (!epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + ciphertext)) { LOG_PRINT_L0("Failed to save file to " << signed_filename); @@ -4875,6 +4904,32 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f return true; } //---------------------------------------------------------------------------------------------------- +std::string wallet2::sign_tx_dump_to_str(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &ptx, signed_tx_set &signed_txes) +{ + // sign the transactions + bool r = sign_tx(exported_txs, ptx, signed_txes); + if (!r) + { + LOG_PRINT_L0("Failed to sign unsigned_tx_set"); + return std::string(); + } + + // save as binary + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << signed_txes; + } + catch(...) + { + return std::string(); + } + LOG_PRINT_L3("Saving signed tx data (with encryption): " << oss.str()); + std::string ciphertext = encrypt_with_view_secret_key(oss.str()); + return std::string(SIGNED_TX_PREFIX) + ciphertext; +} +//---------------------------------------------------------------------------------------------------- bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func) { std::string s; @@ -4892,10 +4947,20 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal LOG_PRINT_L0("Failed to load from " << signed_filename); return false; } + + return parse_tx_from_str(s, ptx, accept_func); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func) +{ + std::string s = signed_tx_st; + boost::system::error_code errcode; + signed_tx_set signed_txs; + const size_t magiclen = strlen(SIGNED_TX_PREFIX) - 1; if (strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen)) { - LOG_PRINT_L0("Bad magic from " << signed_filename); + LOG_PRINT_L0("Bad magic from signed transaction"); return false; } s = s.substr(magiclen); @@ -4911,7 +4976,7 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal } catch (...) { - LOG_PRINT_L0("Failed to parse data from " << signed_filename); + LOG_PRINT_L0("Failed to parse data from signed transaction"); return false; } } @@ -4928,23 +4993,23 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal } catch (...) { - LOG_PRINT_L0("Failed to parse decrypted data from " << signed_filename); + LOG_PRINT_L0("Failed to parse decrypted data from signed transaction"); return false; } } catch (const std::exception &e) { - LOG_PRINT_L0("Failed to decrypt " << signed_filename << ": " << e.what()); + LOG_PRINT_L0("Failed to decrypt signed transaction: " << e.what()); return false; } } else { - LOG_PRINT_L0("Unsupported version in " << signed_filename); + LOG_PRINT_L0("Unsupported version in signed transaction"); return false; } LOG_PRINT_L0("Loaded signed tx data from binary: " << signed_txs.ptx.size() << " transactions"); - for (auto &ptx: signed_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx)); + for (auto &c_ptx: signed_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(c_ptx.tx)); if (accept_func && !accept_func(signed_txs)) { @@ -5021,7 +5086,7 @@ bool wallet2::save_multisig_tx(const multisig_tx_set &txs, const std::string &fi return epee::file_io_utils::save_string_to_file(filename, ciphertext); } //---------------------------------------------------------------------------------------------------- -std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) +wallet2::multisig_tx_set wallet2::make_multisig_tx_set(const std::vector<pending_tx>& ptx_vector) const { multisig_tx_set txs; txs.m_ptx = ptx_vector; @@ -5033,8 +5098,12 @@ std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) } txs.m_signers.insert(get_multisig_signer_public_key()); + return txs; +} - return save_multisig_tx(txs); +std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) +{ + return save_multisig_tx(make_multisig_tx_set(ptx_vector)); } //---------------------------------------------------------------------------------------------------- bool wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) @@ -5666,7 +5735,7 @@ bool wallet2::find_and_save_rings(bool force) for (size_t slice = 0; slice < txs_hashes.size(); slice += SLICE_SIZE) { req.decode_as_json = false; - req.prune = true; + req.prune = false; req.txs_hashes.clear(); size_t ntxes = slice + SLICE_SIZE > txs_hashes.size() ? txs_hashes.size() - slice : SLICE_SIZE; for (size_t s = slice; s < slice + ntxes; ++s) @@ -8422,8 +8491,9 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes } std::vector<std::vector<crypto::signature>> signatures = { std::vector<crypto::signature>(1) }; const size_t sig_len = tools::base58::encode(std::string((const char *)&signatures[0][0], sizeof(crypto::signature))).size(); - THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * sig_len, - error::wallet_internal_error, "incorrect signature size"); + if( sig_str.size() != header_len + num_sigs * sig_len ) { + return false; + } // decode base58 signatures.clear(); @@ -9276,6 +9346,40 @@ bool wallet2::verify(const std::string &data, const cryptonote::account_public_a memcpy(&s, decoded.data(), sizeof(s)); return crypto::check_signature(hash, address.m_spend_public_key, s); } + +std::string wallet2::sign_multisig_participant(const std::string& data) const +{ + CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + + crypto::hash hash; + crypto::cn_fast_hash(data.data(), data.size(), hash); + const cryptonote::account_keys &keys = m_account.get_keys(); + crypto::signature signature; + crypto::generate_signature(hash, get_multisig_signer_public_key(), keys.m_spend_secret_key, signature); + return MULTISIG_SIGNATURE_MAGIC + tools::base58::encode(std::string((const char *)&signature, sizeof(signature))); +} + +bool wallet2::verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const +{ + if (signature.size() < MULTISIG_SIGNATURE_MAGIC.size() || signature.substr(0, MULTISIG_SIGNATURE_MAGIC.size()) != MULTISIG_SIGNATURE_MAGIC) { + MERROR("Signature header check error"); + return false; + } + crypto::hash hash; + crypto::cn_fast_hash(data.data(), data.size(), hash); + std::string decoded; + if (!tools::base58::decode(signature.substr(MULTISIG_SIGNATURE_MAGIC.size()), decoded)) { + MERROR("Signature decoding error"); + return false; + } + crypto::signature s; + if (sizeof(s) != decoded.size()) { + MERROR("Signature decoding error"); + return false; + } + memcpy(&s, decoded.data(), sizeof(s)); + return crypto::check_signature(hash, public_key, s); +} //---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const { @@ -9772,6 +9876,23 @@ std::vector<tools::wallet2::transfer_details> wallet2::export_outputs() const return outs; } //---------------------------------------------------------------------------------------------------- +std::string wallet2::export_outputs_to_str() const +{ + std::vector<tools::wallet2::transfer_details> outs = export_outputs(); + + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << outs; + + std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + std::string header; + header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + std::string ciphertext = encrypt_with_view_secret_key(header + oss.str()); + return magic + ciphertext; +} +//---------------------------------------------------------------------------------------------------- size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs) { m_transfers.clear(); @@ -9804,6 +9925,67 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail return m_transfers.size(); } //---------------------------------------------------------------------------------------------------- +size_t wallet2::import_outputs_from_str(const std::string &outputs_st) +{ + std::string data = outputs_st; + const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC); + if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen)) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad magic from outputs")); + } + + try + { + data = decrypt_with_view_secret_key(std::string(data, magiclen)); + } + catch (const std::exception &e) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt outputs: ") + e.what()); + } + + const size_t headerlen = 2 * sizeof(crypto::public_key); + if (data.size() < headerlen) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad data size for outputs")); + } + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Outputs from are for a different account")); + } + + size_t imported_outputs = 0; + try + { + std::string body(data, headerlen); + std::stringstream iss; + iss << body; + std::vector<tools::wallet2::transfer_details> outputs; + try + { + boost::archive::portable_binary_iarchive ar(iss); + ar >> outputs; + } + catch (...) + { + iss.str(""); + iss << body; + boost::archive::binary_iarchive ar(iss); + ar >> outputs; + } + + imported_outputs = import_outputs(outputs); + } + catch (const std::exception &e) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs") + e.what()); + } + + return imported_outputs; +} +//---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const { crypto::public_key pkey; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 61e6927bc..a10cef561 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -655,6 +655,7 @@ namespace tools bool watch_only() const { return m_watch_only; } bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool has_multisig_partial_key_images() const; + bool has_unknown_key_images() const; bool get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; bool key_on_device() const { return m_key_on_device; } @@ -684,17 +685,23 @@ namespace tools void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) const; + std::string dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) const; std::string save_multisig_tx(multisig_tx_set txs); bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename); std::string save_multisig_tx(const std::vector<pending_tx>& ptx_vector); bool save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); + multisig_tx_set make_multisig_tx_set(const std::vector<pending_tx>& ptx_vector) const; // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL, bool export_raw = false); // sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI bool sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, bool export_raw = false); + bool sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &ptx, signed_tx_set &signed_txs); + std::string sign_tx_dump_to_str(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &ptx, signed_tx_set &signed_txes); // load unsigned_tx_set from file. bool load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) const; + bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const; bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL); + bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func); std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); // pass subaddr_indices by value on purpose std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); @@ -968,9 +975,27 @@ namespace tools std::string sign(const std::string &data) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; + /*! + * \brief sign_multisig_participant signs given message with the multisig public signer key + * \param data message to sign + * \throws if wallet is not multisig + * \return signature + */ + std::string sign_multisig_participant(const std::string& data) const; + /*! + * \brief verify_with_public_key verifies message was signed with given public key + * \param data message + * \param public_key public key to check signature + * \param signature signature of the message + * \return true if the signature is correct + */ + bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const; + // Import/Export wallet data std::vector<tools::wallet2::transfer_details> export_outputs() const; + std::string export_outputs_to_str() const; size_t import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs); + size_t import_outputs_from_str(const std::string &outputs_st); payment_container export_payments() const; void import_payments(const payment_container &payments); void import_payments_out(const std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>> &confirmed_payments); diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index a6ff63dd3..6311e7700 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -82,7 +82,7 @@ namespace wallet_args return i18n_translate(str, "wallet_args"); } - boost::optional<boost::program_options::variables_map> main( + std::pair<boost::optional<boost::program_options::variables_map>, bool> main( int argc, char** argv, const char* const usage, const char* const notice, @@ -127,6 +127,7 @@ namespace wallet_args po::options_description desc_all; desc_all.add(desc_general).add(desc_params); po::variables_map vm; + bool should_terminate = false; bool r = command_line::handle_error_helper(desc_all, [&]() { auto parser = po::command_line_parser(argc, argv).options(desc_all).positional(positional_options); @@ -139,12 +140,14 @@ namespace wallet_args "daemon to work correctly.") << ENDL; Print(print) << wallet_args::tr("Usage:") << ENDL << " " << usage; Print(print) << desc_all; - return false; + should_terminate = true; + return true; } else if (command_line::get_arg(vm, command_line::arg_version)) { Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; - return false; + should_terminate = true; + return true; } if(command_line::has_arg(vm, arg_config_file)) @@ -167,7 +170,10 @@ namespace wallet_args return true; }); if (!r) - return boost::none; + return {boost::none, true}; + + if (should_terminate) + return {std::move(vm), should_terminate}; std::string log_path; if (!command_line::is_arg_defaulted(vm, arg_log_file)) @@ -196,6 +202,6 @@ namespace wallet_args Print(print) << boost::format(wallet_args::tr("Logging to %s")) % log_path; - return {std::move(vm)}; + return {std::move(vm), should_terminate}; } } diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index af6685845..a1f251144 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -44,8 +44,11 @@ namespace wallet_args concurrency. Log file and concurrency arguments are handled, along with basic global init for the wallet process. - \return The list of parsed options, iff there are no errors.*/ - boost::optional<boost::program_options::variables_map> main( + \return + pair.first: The list of parsed options, iff there are no errors. + pair.second: Should the execution terminate succesfully without actually launching the application + */ + std::pair<boost::optional<boost::program_options::variables_map>, bool> main( int argc, char** argv, const char* const usage, const char* const notice, diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index a9d211532..7f7d33642 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -712,7 +712,7 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ template<typename Ts, typename Tu> bool wallet_rpc_server::fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, - bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, bool do_not_relay, + bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er) { for (const auto & ptx : ptx_vector) @@ -741,7 +741,16 @@ namespace tools } else { - if (!do_not_relay) + if (m_wallet->watch_only()){ + unsigned_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->dump_tx_to_str(ptx_vector)); + if (unsigned_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save unsigned tx set after creation"; + return false; + } + } + else if (!do_not_relay) m_wallet->commit_tx(ptx_vector); // populate response with tx hashes @@ -811,7 +820,7 @@ namespace tools return false; } - return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er); } catch (const std::exception& e) @@ -858,7 +867,7 @@ namespace tools 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, m_trusted_daemon); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) @@ -869,6 +878,141 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->key_on_device()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "command not supported by HW wallet"; + return false; + } + if(m_wallet->watch_only()) + { + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; + er.message = "command not supported by watch-only wallet"; + return false; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + tools::wallet2::unsigned_tx_set exported_txs; + if(!m_wallet->parse_unsigned_tx_from_str(blob, exported_txs)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA; + er.message = "cannot load unsigned_txset"; + return false; + } + + std::vector<tools::wallet2::pending_tx> ptxs; + try + { + tools::wallet2::signed_tx_set signed_txs; + std::string ciphertext = m_wallet->sign_tx_dump_to_str(exported_txs, ptxs, signed_txs); + if (ciphertext.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED; + er.message = "Failed to sign unsigned tx"; + return false; + } + + res.signed_txset = epee::string_tools::buff_to_hex_nodelimer(ciphertext); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED; + er.message = std::string("Failed to sign unsigned tx: ") + e.what(); + return false; + } + + for (auto &ptx: ptxs) + { + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + } + + if (req.export_raw) + { + for (auto &ptx: ptxs) + { + res.tx_raw_list.push_back(epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(ptx.tx))); + } + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->key_on_device()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "command not supported by HW wallet"; + return false; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + std::vector<tools::wallet2::pending_tx> ptx_vector; + try + { + bool r = m_wallet->parse_tx_from_str(blob, ptx_vector, NULL); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA; + er.message = "Failed to parse signed tx data."; + return false; + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA; + er.message = std::string("Failed to parse signed tx: ") + e.what(); + return false; + } + + try + { + for (auto &ptx: ptx_vector) + { + m_wallet->commit_tx(ptx); + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_SIGNED_SUBMISSION; + er.message = std::string("Failed to submit signed tx: ") + e.what(); + return false; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); @@ -883,7 +1027,7 @@ namespace tools { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) @@ -931,7 +1075,7 @@ namespace tools uint32_t priority = m_wallet->adjust_priority(req.priority); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) @@ -1007,7 +1151,7 @@ namespace tools return false; } - return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er); } catch (const std::exception& e) @@ -1974,6 +2118,72 @@ namespace tools return false; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_export_outputs(const wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->key_on_device()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "command not supported by HW wallet"; + return false; + } + + try + { + res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str()); + } + 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_import_outputs(const wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->key_on_device()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "command not supported by HW wallet"; + return false; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.outputs_data_hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + try + { + res.num_imported = m_wallet->import_outputs_from_str(blob); + } + 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_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); @@ -2876,6 +3086,12 @@ namespace tools 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) + { + res.version = WALLET_RPC_VERSION; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ } int main(int argc, char** argv) { @@ -2895,7 +3111,9 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_wallet_dir); command_line::add_arg(desc_params, arg_prompt_for_password); - const auto vm = wallet_args::main( + boost::optional<po::variables_map> vm; + bool should_terminate = false; + std::tie(vm, should_terminate) = wallet_args::main( argc, argv, "monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]", tools::wallet_rpc_server::tr("This is the RPC monero wallet. It needs to connect to a monero\ndaemon to work correctly."), @@ -2909,6 +3127,10 @@ int main(int argc, char** argv) { { return 1; } + if (should_terminate) + { + return 0; + } std::unique_ptr<tools::wallet2> wal; try diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 2ec53cc80..9cb67c593 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -84,6 +84,8 @@ namespace tools MAP_JON_RPC_WE("getheight", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT) MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) + MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER) + MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER) MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) MAP_JON_RPC_WE("sweep_unmixable", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) MAP_JON_RPC_WE("sweep_all", on_sweep_all, wallet_rpc::COMMAND_RPC_SWEEP_ALL) @@ -114,6 +116,8 @@ namespace tools MAP_JON_RPC_WE("get_transfer_by_txid", on_get_transfer_by_txid, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID) MAP_JON_RPC_WE("sign", on_sign, wallet_rpc::COMMAND_RPC_SIGN) MAP_JON_RPC_WE("verify", on_verify, wallet_rpc::COMMAND_RPC_VERIFY) + MAP_JON_RPC_WE("export_outputs", on_export_outputs, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS) + MAP_JON_RPC_WE("import_outputs", on_import_outputs, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS) MAP_JON_RPC_WE("export_key_images", on_export_key_images, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES) MAP_JON_RPC_WE("import_key_images", on_import_key_images, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES) MAP_JON_RPC_WE("make_uri", on_make_uri, wallet_rpc::COMMAND_RPC_MAKE_URI) @@ -135,6 +139,7 @@ namespace tools MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG) MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG) MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG) + MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION) END_JSON_RPC_MAP() END_URI_MAP2() @@ -154,6 +159,8 @@ 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); bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er); bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er); + bool on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er); + bool on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er); bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er); bool on_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er); bool on_sweep_single(const wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::request& req, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::response& res, epee::json_rpc::error& er); @@ -182,6 +189,8 @@ namespace tools bool on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er); bool on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er); bool on_verify(const wallet_rpc::COMMAND_RPC_VERIFY::request& req, wallet_rpc::COMMAND_RPC_VERIFY::response& res, epee::json_rpc::error& er); + bool on_export_outputs(const wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::response& res, epee::json_rpc::error& er); + bool on_import_outputs(const wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::response& res, epee::json_rpc::error& er); bool on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er); bool on_import_key_images(const wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er); bool on_make_uri(const wallet_rpc::COMMAND_RPC_MAKE_URI::request& req, wallet_rpc::COMMAND_RPC_MAKE_URI::response& res, epee::json_rpc::error& er); @@ -203,6 +212,7 @@ namespace tools bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er); bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er); bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); @@ -217,7 +227,7 @@ namespace tools template<typename Ts, typename Tu> bool fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, - bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, bool do_not_relay, + bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er); wallet2 *m_wallet; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index a0f43c9b9..96e135f01 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -39,6 +39,17 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" +// When making *any* change here, bump minor +// If the change is incompatible, then bump major and set minor to 0 +// This ensures WALLET_RPC_VERSION always increases, that every change +// has its own version, and that clients can just test major to see +// whether they can talk to a given wallet without having to know in +// 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 0 +#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 { namespace wallet_rpc @@ -419,6 +430,7 @@ namespace wallet_rpc std::string tx_blob; std::string tx_metadata; std::string multisig_txset; + std::string unsigned_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -429,6 +441,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) END_KV_SERIALIZE_MAP() }; }; @@ -484,6 +497,7 @@ namespace wallet_rpc std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; std::string multisig_txset; + std::string unsigned_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -493,6 +507,55 @@ namespace wallet_rpc KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SIGN_TRANSFER + { + struct request + { + std::string unsigned_txset; + bool export_raw; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE_OPT(export_raw, false) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string signed_txset; + std::list<std::string> tx_hash_list; + std::list<std::string> tx_raw_list; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(signed_txset) + KV_SERIALIZE(tx_hash_list) + KV_SERIALIZE(tx_raw_list) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SUBMIT_TRANSFER + { + struct request + { + std::string tx_data_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_data_hex) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::list<std::string> tx_hash_list; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash_list) END_KV_SERIALIZE_MAP() }; }; @@ -532,6 +595,7 @@ namespace wallet_rpc std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; std::string multisig_txset; + std::string unsigned_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -541,6 +605,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) END_KV_SERIALIZE_MAP() }; }; @@ -598,6 +663,7 @@ namespace wallet_rpc std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; std::string multisig_txset; + std::string unsigned_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -607,6 +673,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) END_KV_SERIALIZE_MAP() }; }; @@ -651,6 +718,7 @@ namespace wallet_rpc std::string tx_blob; std::string tx_metadata; std::string multisig_txset; + std::string unsigned_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -660,6 +728,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) + KV_SERIALIZE(unsigned_txset) END_KV_SERIALIZE_MAP() }; }; @@ -1364,6 +1433,45 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_EXPORT_OUTPUTS + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string outputs_data_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outputs_data_hex); + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_IMPORT_OUTPUTS + { + struct request + { + std::string outputs_data_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outputs_data_hex); + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t num_imported; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(num_imported); + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_EXPORT_KEY_IMAGES { struct request @@ -1848,5 +1956,23 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_VERSION + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint32_t version; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(version) + END_KV_SERIALIZE_MAP() + }; + }; + } } diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index d47467940..f127ae240 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -69,3 +69,7 @@ #define WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION -36 #define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_UNLOCKED_MONEY -37 #define WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION -38 +#define WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA -39 +#define WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA -40 +#define WALLET_RPC_ERROR_CODE_SIGNED_SUBMISSION -41 +#define WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED -42 |