diff options
Diffstat (limited to 'src')
426 files changed, 4616 insertions, 4177 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3335d3c21..fed042ce2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/blockchain_db/CMakeLists.txt b/src/blockchain_db/CMakeLists.txt index 14ed76295..b1525d6d7 100644 --- a/src/blockchain_db/CMakeLists.txt +++ b/src/blockchain_db/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index ab73e255c..3d52d5fb9 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 263948fa2..835d6dcad 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -1883,16 +1883,18 @@ public: } virtual ~db_txn_guard() { - if (active) - stop(); + stop(); } void stop() { - if (readonly) - db->block_rtxn_stop(); - else - db->block_wtxn_stop(); - active = false; + if (active) + { + if (readonly) + db->block_rtxn_stop(); + else + db->block_wtxn_stop(); + active = false; + } } void abort() { diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index db7fa6c7c..4178c862b 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -465,6 +465,32 @@ void mdb_txn_safe::increment_txns(int i) num_active_txns += i; } +#define TXN_PREFIX(flags); \ + mdb_txn_safe auto_txn; \ + mdb_txn_safe* txn_ptr = &auto_txn; \ + if (m_batch_active) \ + txn_ptr = m_write_txn; \ + else \ + { \ + if (auto mdb_res = lmdb_txn_begin(m_env, NULL, flags, auto_txn)) \ + throw0(DB_ERROR(lmdb_error(std::string("Failed to create a transaction for the db in ")+__FUNCTION__+": ", mdb_res).c_str())); \ + } \ + +#define TXN_PREFIX_RDONLY() \ + MDB_txn *m_txn; \ + mdb_txn_cursors *m_cursors; \ + mdb_txn_safe auto_txn; \ + bool my_rtxn = block_rtxn_start(&m_txn, &m_cursors); \ + if (my_rtxn) auto_txn.m_tinfo = m_tinfo.get(); \ + else auto_txn.uncheck() +#define TXN_POSTFIX_RDONLY() + +#define TXN_POSTFIX_SUCCESS() \ + do { \ + if (! m_batch_active) \ + auto_txn.commit(); \ + } while(0) + void lmdb_resized(MDB_env *env, int isactive) { mdb_txn_safe::prevent_new_txns(); @@ -713,21 +739,20 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks, uin } else { - MDB_txn *rtxn; - mdb_txn_cursors *rcurs; - bool my_rtxn = block_rtxn_start(&rtxn, &rcurs); - for (uint64_t block_num = block_start; block_num <= block_stop; ++block_num) { - // we have access to block weight, which will be greater or equal to block size, - // so use this as a proxy. If it's too much off, we might have to check actual size, - // which involves reading more data, so is not really wanted - size_t block_weight = get_block_weight(block_num); - total_block_size += block_weight; - // Track number of blocks being totalled here instead of assuming, in case - // some blocks were to be skipped for being outliers. - ++num_blocks_used; + TXN_PREFIX_RDONLY(); + for (uint64_t block_num = block_start; block_num <= block_stop; ++block_num) + { + // we have access to block weight, which will be greater or equal to block size, + // so use this as a proxy. If it's too much off, we might have to check actual size, + // which involves reading more data, so is not really wanted + size_t block_weight = get_block_weight(block_num); + total_block_size += block_weight; + // Track number of blocks being totalled here instead of assuming, in case + // some blocks were to be skipped for being outliers. + ++num_blocks_used; + } } - if (my_rtxn) block_rtxn_stop(); avg_block_size = total_block_size / (num_blocks_used ? num_blocks_used : 1); MDEBUG("average block size across recent " << num_blocks_used << " blocks: " << avg_block_size); } @@ -1678,32 +1703,6 @@ void BlockchainLMDB::unlock() check_open(); } -#define TXN_PREFIX(flags); \ - mdb_txn_safe auto_txn; \ - mdb_txn_safe* txn_ptr = &auto_txn; \ - if (m_batch_active) \ - txn_ptr = m_write_txn; \ - else \ - { \ - if (auto mdb_res = lmdb_txn_begin(m_env, NULL, flags, auto_txn)) \ - throw0(DB_ERROR(lmdb_error(std::string("Failed to create a transaction for the db in ")+__FUNCTION__+": ", mdb_res).c_str())); \ - } \ - -#define TXN_PREFIX_RDONLY() \ - MDB_txn *m_txn; \ - mdb_txn_cursors *m_cursors; \ - mdb_txn_safe auto_txn; \ - bool my_rtxn = block_rtxn_start(&m_txn, &m_cursors); \ - if (my_rtxn) auto_txn.m_tinfo = m_tinfo.get(); \ - else auto_txn.uncheck() -#define TXN_POSTFIX_RDONLY() - -#define TXN_POSTFIX_SUCCESS() \ - do { \ - if (! m_batch_active) \ - auto_txn.commit(); \ - } while(0) - // The below two macros are for DB access within block add/remove, whether // regular batch txn is in use or not. m_write_txn is used as a batch txn, even @@ -3923,13 +3922,20 @@ void BlockchainLMDB::block_rtxn_stop() const LOG_PRINT_L3("BlockchainLMDB::" << __func__); mdb_txn_reset(m_tinfo->m_ti_rtxn); memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); + /* cancel out the increment from rtxn_start */ + mdb_txn_safe::increment_txns(-1); } bool BlockchainLMDB::block_rtxn_start() const { MDB_txn *mtxn; mdb_txn_cursors *mcur; - return block_rtxn_start(&mtxn, &mcur); + /* auto_txn is only used for the create gate */ + mdb_txn_safe auto_txn; + bool ret = block_rtxn_start(&mtxn, &mcur); + if (ret) + auto_txn.increment_txns(1); /* remember there is an active readtxn */ + return ret; } void BlockchainLMDB::block_wtxn_start() diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index bdae44948..c352458b4 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/blockchain_db/locked_txn.h b/src/blockchain_db/locked_txn.h index beab8af21..6170d7b5a 100644 --- a/src/blockchain_db/locked_txn.h +++ b/src/blockchain_db/locked_txn.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index fe8078d5e..946f26270 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index 8122d9034..224a9e63c 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/blockchain_utilities/README.md b/src/blockchain_utilities/README.md index a7d47e233..4fcd138c4 100644 --- a/src/blockchain_utilities/README.md +++ b/src/blockchain_utilities/README.md @@ -1,6 +1,6 @@ # Monero Blockchain Utilities -Copyright (c) 2014-2022, The Monero Project +Copyright (c) 2014-2023, The Monero Project ## Introduction diff --git a/src/blockchain_utilities/blockchain_ancestry.cpp b/src/blockchain_utilities/blockchain_ancestry.cpp index b0964e4a3..66dd7813b 100644 --- a/src/blockchain_utilities/blockchain_ancestry.cpp +++ b/src/blockchain_utilities/blockchain_ancestry.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_blackball.cpp b/src/blockchain_utilities/blockchain_blackball.cpp index dee0f7f2a..83051e290 100644 --- a/src/blockchain_utilities/blockchain_blackball.cpp +++ b/src/blockchain_utilities/blockchain_blackball.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_depth.cpp b/src/blockchain_utilities/blockchain_depth.cpp index b98a1f8e2..6a06e0a96 100644 --- a/src/blockchain_utilities/blockchain_depth.cpp +++ b/src/blockchain_utilities/blockchain_depth.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp index 82fe524de..3d7b3f61a 100644 --- a/src/blockchain_utilities/blockchain_export.cpp +++ b/src/blockchain_utilities/blockchain_export.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index f8cca638d..f75a1e827 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -40,7 +40,6 @@ #include "blocks/blocks.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "serialization/binary_utils.h" // dump_binary(), parse_binary() -#include "serialization/json_utils.h" // dump_json() #include "include_base_utils.h" #include "cryptonote_core/cryptonote_core.h" diff --git a/src/blockchain_utilities/blockchain_prune.cpp b/src/blockchain_utilities/blockchain_prune.cpp index 4a91cf7cc..1e4b48b73 100644 --- a/src/blockchain_utilities/blockchain_prune.cpp +++ b/src/blockchain_utilities/blockchain_prune.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. @@ -52,9 +52,11 @@ using namespace cryptonote; static std::string db_path; // default to fast:1 -static uint64_t records_per_sync = 128; +static uint64_t records_per_sync = 16 * 65536; static const size_t slack = 512 * 1024 * 1024; +static std::vector<bool> is_v1; + static std::error_code replace_file(const boost::filesystem::path& replacement_name, const boost::filesystem::path& replaced_name) { std::error_code ec = tools::replace_file(replacement_name.string(), replaced_name.string()); @@ -89,6 +91,14 @@ static void close(MDB_env *env) mdb_env_close(env); } +static void mark_v1_tx(const MDB_val &k, const MDB_val &v) +{ + const uint64_t tx_id = *(const uint64_t*)k.mv_data; + if (tx_id >= is_v1.size()) + is_v1.resize(tx_id + 1, false); + is_v1[tx_id] = cryptonote::is_v1_tx(cryptonote::blobdata_ref{(const char*)v.mv_data, v.mv_size}); +} + static void add_size(MDB_env *env, uint64_t bytes) { try @@ -136,7 +146,7 @@ static void check_resize(MDB_env *env, size_t bytes) add_size(env, size_used + bytes + 2 * slack - mei.me_mapsize); } -static bool resize_point(size_t nrecords, MDB_env *env, MDB_txn **txn, size_t &bytes) +static bool resize_point(size_t &nrecords, MDB_env *env, MDB_txn **txn, size_t &bytes) { if (nrecords % records_per_sync && bytes <= slack / 2) return false; @@ -146,10 +156,11 @@ static bool resize_point(size_t nrecords, MDB_env *env, MDB_txn **txn, size_t &b dbr = mdb_txn_begin(env, NULL, 0, txn); if (dbr) throw std::runtime_error("Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); bytes = 0; + nrecords = 0; return true; } -static void copy_table(MDB_env *env0, MDB_env *env1, const char *table, unsigned int flags, unsigned int putflags, int (*cmp)(const MDB_val*, const MDB_val*)=0) +static void copy_table(MDB_env *env0, MDB_env *env1, const char *table, unsigned int flags, unsigned int putflags, int (*cmp)(const MDB_val*, const MDB_val*)=0, void (*f)(const MDB_val&, const MDB_val&) = 0) { MDB_dbi dbi0, dbi1; MDB_txn *txn0, *txn1; @@ -200,6 +211,11 @@ static void copy_table(MDB_env *env0, MDB_env *env1, const char *table, unsigned dbr = mdb_cursor_open(txn1, dbi1, &cur1); if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr))); + if (flags & MDB_DUPSORT) + putflags |= MDB_APPENDDUP; + else + putflags |= MDB_APPEND; + MDB_val k; MDB_val v; MDB_cursor_op op = MDB_FIRST; @@ -214,7 +230,8 @@ static void copy_table(MDB_env *env0, MDB_env *env1, const char *table, unsigned throw std::runtime_error("Failed to enumerate " + std::string(table) + " records: " + std::string(mdb_strerror(ret))); bytes += k.mv_size + v.mv_size; - if (resize_point(++nrecords, env1, &txn1, bytes)) + ++nrecords; + if (resize_point(nrecords, env1, &txn1, bytes)) { dbr = mdb_cursor_open(txn1, dbi1, &cur1); if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr))); @@ -223,6 +240,9 @@ static void copy_table(MDB_env *env0, MDB_env *env1, const char *table, unsigned ret = mdb_cursor_put(cur1, &k, &v, putflags); if (ret) throw std::runtime_error("Failed to write " + std::string(table) + " record: " + std::string(mdb_strerror(ret))); + + if (f) + (*f)(k, v); } mdb_cursor_close(cur1); @@ -235,17 +255,6 @@ static void copy_table(MDB_env *env0, MDB_env *env1, const char *table, unsigned mdb_dbi_close(env0, dbi0); } -static bool is_v1_tx(MDB_cursor *c_txs_pruned, MDB_val *tx_id) -{ - MDB_val v; - int ret = mdb_cursor_get(c_txs_pruned, tx_id, &v, MDB_SET); - if (ret) - throw std::runtime_error("Failed to find transaction pruned data: " + std::string(mdb_strerror(ret))); - if (v.mv_size == 0) - throw std::runtime_error("Invalid transaction pruned data"); - return cryptonote::is_v1_tx(cryptonote::blobdata_ref{(const char*)v.mv_data, v.mv_size}); -} - static void prune(MDB_env *env0, MDB_env *env1) { MDB_dbi dbi0_blocks, dbi0_txs_pruned, dbi0_txs_prunable, dbi0_tx_indices, dbi1_txs_prunable, dbi1_txs_prunable_tip, dbi1_properties; @@ -324,7 +333,10 @@ static void prune(MDB_env *env0, MDB_env *env1) mdb_dbi_close(env0, dbi0_blocks); const uint64_t blockchain_height = stats.ms_entries; size_t nrecords = 0, bytes = 0; + std::vector<bool> prunable_needed; + // go through all txes tx indices, recording which ones should have their prunable part retained + MINFO("Marking prunable txes"); MDB_cursor_op op = MDB_FIRST; while (1) { @@ -336,7 +348,8 @@ static void prune(MDB_env *env0, MDB_env *env1) const txindex *ti = (const txindex*)v.mv_data; const uint64_t block_height = ti->data.block_id; - MDB_val_set(kk, ti->data.tx_id); + const uint64_t tx_id = ti->data.tx_id; + MDB_val_set(kk, tx_id); if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height) { MDEBUG(block_height << "/" << blockchain_height << " is in tip"); @@ -344,22 +357,23 @@ static void prune(MDB_env *env0, MDB_env *env1) dbr = mdb_cursor_put(cur1_txs_prunable_tip, &kk, &vv, 0); if (dbr) throw std::runtime_error("Failed to write prunable tx tip data: " + std::string(mdb_strerror(dbr))); bytes += kk.mv_size + vv.mv_size; - } - if (tools::has_unpruned_block(block_height, blockchain_height, pruning_seed) || is_v1_tx(cur0_txs_pruned, &kk)) - { - MDB_val vv; - dbr = mdb_cursor_get(cur0_txs_prunable, &kk, &vv, MDB_SET); - if (dbr) throw std::runtime_error("Failed to read prunable tx data: " + std::string(mdb_strerror(dbr))); - bytes += kk.mv_size + vv.mv_size; - if (resize_point(++nrecords, env1, &txn1, bytes)) + + ++nrecords; + if (resize_point(nrecords, env1, &txn1, bytes)) { dbr = mdb_cursor_open(txn1, dbi1_txs_prunable, &cur1_txs_prunable); if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr))); dbr = mdb_cursor_open(txn1, dbi1_txs_prunable_tip, &cur1_txs_prunable_tip); if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr))); } - dbr = mdb_cursor_put(cur1_txs_prunable, &kk, &vv, 0); - if (dbr) throw std::runtime_error("Failed to write prunable tx data: " + std::string(mdb_strerror(dbr))); + } + if (tx_id >= is_v1.size()) + throw std::runtime_error("tx_id out of range of is_v1 vector"); + if (tools::has_unpruned_block(block_height, blockchain_height, pruning_seed) || is_v1[tx_id]) + { + if (tx_id >= prunable_needed.size()) + prunable_needed.resize(tx_id + 1, false); + prunable_needed[tx_id] = true; } else { @@ -367,6 +381,37 @@ static void prune(MDB_env *env0, MDB_env *env1) } } + // go through prunable parts, carrying over those we need + MINFO("Copying retained prunable data"); + op = MDB_FIRST; + while (1) + { + int ret = mdb_cursor_get(cur0_txs_prunable, &k, &v, op); + op = MDB_NEXT; + if (ret == MDB_NOTFOUND) + break; + if (ret) throw std::runtime_error("Failed to enumerate records: " + std::string(mdb_strerror(ret))); + + const uint64_t tx_id = *(const uint64_t*)k.mv_data; + if (tx_id >= prunable_needed.size()) + throw std::runtime_error("tx_id out of range of prunable_needed vector"); + if (prunable_needed[tx_id]) + { + dbr = mdb_cursor_put(cur1_txs_prunable, &k, &v, MDB_APPEND); + if (dbr) throw std::runtime_error("Failed to write prunable tx data: " + std::string(mdb_strerror(dbr))); + + bytes += k.mv_size + v.mv_size; + ++nrecords; + if (resize_point(nrecords, env1, &txn1, bytes)) + { + dbr = mdb_cursor_open(txn1, dbi1_txs_prunable, &cur1_txs_prunable); + if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr))); + dbr = mdb_cursor_open(txn1, dbi1_txs_prunable_tip, &cur1_txs_prunable_tip); + if (dbr) throw std::runtime_error("Failed to create LMDB cursor: " + std::string(mdb_strerror(dbr))); + } + } + } + mdb_cursor_close(cur1_txs_prunable_tip); mdb_cursor_close(cur1_txs_prunable); mdb_cursor_close(cur0_txs_prunable); @@ -419,7 +464,7 @@ static bool parse_db_sync_mode(std::string db_sync_mode, uint64_t &db_flags) else if(options[0] == "fastest") { db_flags = DBF_FASTEST; - records_per_sync = 1000; // default to fastest:async:1000 + // default to fastest:async:N } else return false; @@ -455,7 +500,7 @@ int main(int argc, char* argv[]) const command_line::arg_descriptor<std::string> arg_db_sync_mode = { "db-sync-mode" , "Specify sync option, using format [safe|fast|fastest]:[nrecords_per_sync]." - , "fast:1000" + , "fast:" + std::to_string(records_per_sync) }; const command_line::arg_descriptor<bool> arg_copy_pruned_database = {"copy-pruned-database", "Copy database anyway if already pruned"}; @@ -601,26 +646,27 @@ int main(int argc, char* argv[]) MDB_env *env0 = NULL, *env1 = NULL; open(env0, paths[0], db_flags, true); open(env1, paths[1], db_flags, false); - copy_table(env0, env1, "blocks", MDB_INTEGERKEY, MDB_APPEND); - copy_table(env0, env1, "block_info", MDB_INTEGERKEY | MDB_DUPSORT| MDB_DUPFIXED, MDB_APPENDDUP, BlockchainLMDB::compare_uint64); + copy_table(env0, env1, "blocks", MDB_INTEGERKEY, 0); + copy_table(env0, env1, "block_info", MDB_INTEGERKEY | MDB_DUPSORT| MDB_DUPFIXED, 0, BlockchainLMDB::compare_uint64); copy_table(env0, env1, "block_heights", MDB_INTEGERKEY | MDB_DUPSORT| MDB_DUPFIXED, 0, BlockchainLMDB::compare_hash32); //copy_table(env0, env1, "txs", MDB_INTEGERKEY); - copy_table(env0, env1, "txs_pruned", MDB_INTEGERKEY, MDB_APPEND); - copy_table(env0, env1, "txs_prunable_hash", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, MDB_APPEND); + copy_table(env0, env1, "txs_pruned", MDB_INTEGERKEY, 0, NULL, &mark_v1_tx); + copy_table(env0, env1, "txs_prunable_hash", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, 0); // not copied: prunable, prunable_tip copy_table(env0, env1, "tx_indices", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, 0, BlockchainLMDB::compare_hash32); - copy_table(env0, env1, "tx_outputs", MDB_INTEGERKEY, MDB_APPEND); - copy_table(env0, env1, "output_txs", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, MDB_APPENDDUP, BlockchainLMDB::compare_uint64); - copy_table(env0, env1, "output_amounts", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, MDB_APPENDDUP, BlockchainLMDB::compare_uint64); - copy_table(env0, env1, "spent_keys", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, MDB_NODUPDATA, BlockchainLMDB::compare_hash32); - copy_table(env0, env1, "txpool_meta", 0, MDB_NODUPDATA, BlockchainLMDB::compare_hash32); - copy_table(env0, env1, "txpool_blob", 0, MDB_NODUPDATA, BlockchainLMDB::compare_hash32); - copy_table(env0, env1, "hf_versions", MDB_INTEGERKEY, MDB_APPEND); + copy_table(env0, env1, "tx_outputs", MDB_INTEGERKEY, 0); + copy_table(env0, env1, "output_txs", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, 0, BlockchainLMDB::compare_uint64); + copy_table(env0, env1, "output_amounts", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, 0, BlockchainLMDB::compare_uint64); + copy_table(env0, env1, "spent_keys", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, 0, BlockchainLMDB::compare_hash32); + copy_table(env0, env1, "txpool_meta", 0, 0, BlockchainLMDB::compare_hash32); + copy_table(env0, env1, "txpool_blob", 0, 0, BlockchainLMDB::compare_hash32); + copy_table(env0, env1, "alt_blocks", 0, 0, BlockchainLMDB::compare_hash32); + copy_table(env0, env1, "hf_versions", MDB_INTEGERKEY, 0); copy_table(env0, env1, "properties", 0, 0, BlockchainLMDB::compare_string); if (already_pruned) { - copy_table(env0, env1, "txs_prunable", MDB_INTEGERKEY, MDB_APPEND, BlockchainLMDB::compare_uint64); - copy_table(env0, env1, "txs_prunable_tip", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, MDB_NODUPDATA, BlockchainLMDB::compare_uint64); + copy_table(env0, env1, "txs_prunable", MDB_INTEGERKEY, 0, BlockchainLMDB::compare_uint64); + copy_table(env0, env1, "txs_prunable_tip", MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, 0, BlockchainLMDB::compare_uint64); } else { diff --git a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp index 05aaf42ee..4da9c15c1 100644 --- a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp +++ b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index 3009b5024..5e4245ebd 100644 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -46,6 +46,77 @@ using namespace cryptonote; static bool stop_requested = false; +static bool do_inputs, do_outputs, do_ringsize, do_hours, do_emission, do_fees, do_diff; + +static struct tm prevtm, currtm; +static uint64_t prevsz, currsz; +static uint64_t prevtxs, currtxs; +static uint64_t currblks; +static uint64_t h; +static uint64_t totins, totouts, totrings; +static boost::multiprecision::uint128_t prevemission, prevfees; +static boost::multiprecision::uint128_t emission, fees; +static boost::multiprecision::uint128_t totdiff, mindiff, maxdiff; + +#define MAX_INOUT 0xffffffff +#define MAX_RINGS 0xffffffff + +static uint32_t minins = MAX_INOUT, maxins; +static uint32_t minouts = MAX_INOUT, maxouts; +static uint32_t minrings = MAX_RINGS, maxrings; +static uint32_t io, tottxs; +static uint32_t txhr[24]; + +static void doprint() +{ + char timebuf[64]; + + strftime(timebuf, sizeof(timebuf), "%Y-%m-%d", &prevtm); + prevtm = currtm; + std::cout << timebuf << "\t" << currblks << "\t" << h << "\t" << currtxs << "\t" << prevtxs + currtxs << "\t" << currsz << "\t" << prevsz + currsz; + prevsz += currsz; + currsz = 0; + prevtxs += currtxs; + currtxs = 0; + if (!tottxs) + tottxs = 1; + if (do_emission) { + std::cout << "\t" << print_money(emission) << "\t" << print_money(prevemission + emission); + prevemission += emission; + emission = 0; + } + if (do_fees) { + std::cout << "\t" << print_money(fees) << "\t" << print_money(prevfees + fees); + prevfees += fees; + fees = 0; + } + if (do_diff) { + std::cout << "\t" << (maxdiff ? mindiff : 0) << "\t" << maxdiff << "\t" << totdiff / currblks; + mindiff = 0; maxdiff = 0; totdiff = 0; + } + if (do_inputs) { + std::cout << "\t" << (maxins ? minins : 0) << "\t" << maxins << "\t" << totins * 1.0 / tottxs; + minins = MAX_INOUT; maxins = 0; totins = 0; + } + if (do_outputs) { + std::cout << "\t" << (maxouts ? minouts : 0) << "\t" << maxouts << "\t" << totouts * 1.0 / tottxs; + minouts = MAX_INOUT; maxouts = 0; totouts = 0; + } + if (do_ringsize) { + std::cout << "\t" << (maxrings ? minrings : 0) << "\t" << maxrings << "\t" << totrings * 1.0 / tottxs; + minrings = MAX_RINGS; maxrings = 0; totrings = 0; + } + if (do_hours) { + for (int i=0; i<24; i++) { + std::cout << "\t" << txhr[i]; + txhr[i] = 0; + } + } + currblks = 0; + tottxs = 0; + std::cout << ENDL; +} + int main(int argc, char* argv[]) { TRY_ENTRY(); @@ -123,13 +194,13 @@ int main(int argc, char* argv[]) network_type net_type = opt_testnet ? TESTNET : opt_stagenet ? STAGENET : MAINNET; block_start = command_line::get_arg(vm, arg_block_start); block_stop = command_line::get_arg(vm, arg_block_stop); - bool do_inputs = command_line::get_arg(vm, arg_inputs); - bool do_outputs = command_line::get_arg(vm, arg_outputs); - bool do_ringsize = command_line::get_arg(vm, arg_ringsize); - bool do_hours = command_line::get_arg(vm, arg_hours); - bool do_emission = command_line::get_arg(vm, arg_emission); - bool do_fees = command_line::get_arg(vm, arg_fees); - bool do_diff = command_line::get_arg(vm, arg_diff); + do_inputs = command_line::get_arg(vm, arg_inputs); + do_outputs = command_line::get_arg(vm, arg_outputs); + do_ringsize = command_line::get_arg(vm, arg_ringsize); + do_hours = command_line::get_arg(vm, arg_hours); + do_emission = command_line::get_arg(vm, arg_emission); + do_fees = command_line::get_arg(vm, arg_fees); + do_diff = command_line::get_arg(vm, arg_diff); LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); std::unique_ptr<Blockchain> core_storage; @@ -211,25 +282,7 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' } std::cout << ENDL; -#define MAX_INOUT 0xffffffff -#define MAX_RINGS 0xffffffff - - struct tm prevtm = {0}, currtm; - uint64_t prevsz = 0, currsz = 0; - uint64_t prevtxs = 0, currtxs = 0; - uint64_t currblks = 0; - uint64_t totins = 0, totouts = 0, totrings = 0; - boost::multiprecision::uint128_t prevemission = 0, prevfees = 0; - boost::multiprecision::uint128_t emission = 0, fees = 0; - boost::multiprecision::uint128_t totdiff = 0, mindiff = 0, maxdiff = 0; - uint32_t minins = MAX_INOUT, maxins = 0; - uint32_t minouts = MAX_INOUT, maxouts = 0; - uint32_t minrings = MAX_RINGS, maxrings = 0; - uint32_t io, tottxs = 0; - uint32_t txhr[24] = {0}; - unsigned int i; - - for (uint64_t h = block_start; h < block_stop; ++h) + for (h = block_start; h < block_stop; ++h) { cryptonote::blobdata bd = db->get_block_blob_from_height(h); cryptonote::block blk; @@ -239,7 +292,6 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' return 1; } time_t tt = blk.timestamp; - char timebuf[64]; epee::misc_utils::get_gmt_time(tt, currtm); if (!prevtm.tm_year) prevtm = currtm; @@ -247,54 +299,9 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' if (currtm.tm_mday > prevtm.tm_mday || (currtm.tm_mday == 1 && prevtm.tm_mday > 27)) { // check for timestamp fudging around month ends - if (prevtm.tm_mday == 1 && currtm.tm_mday > 27) - goto skip; - strftime(timebuf, sizeof(timebuf), "%Y-%m-%d", &prevtm); - prevtm = currtm; - std::cout << timebuf << "\t" << currblks << "\t" << h << "\t" << currtxs << "\t" << prevtxs + currtxs << "\t" << currsz << "\t" << prevsz + currsz; - prevsz += currsz; - currsz = 0; - prevtxs += currtxs; - currtxs = 0; - if (!tottxs) - tottxs = 1; - if (do_emission) { - std::cout << "\t" << print_money(emission) << "\t" << print_money(prevemission + emission); - prevemission += emission; - emission = 0; - } - if (do_fees) { - std::cout << "\t" << print_money(fees) << "\t" << print_money(prevfees + fees); - prevfees += fees; - fees = 0; - } - if (do_diff) { - std::cout << "\t" << (maxdiff ? mindiff : 0) << "\t" << maxdiff << "\t" << totdiff / currblks; - mindiff = 0; maxdiff = 0; totdiff = 0; - } - if (do_inputs) { - std::cout << "\t" << (maxins ? minins : 0) << "\t" << maxins << "\t" << totins * 1.0 / tottxs; - minins = MAX_INOUT; maxins = 0; totins = 0; - } - if (do_outputs) { - std::cout << "\t" << (maxouts ? minouts : 0) << "\t" << maxouts << "\t" << totouts * 1.0 / tottxs; - minouts = MAX_INOUT; maxouts = 0; totouts = 0; - } - if (do_ringsize) { - std::cout << "\t" << (maxrings ? minrings : 0) << "\t" << maxrings << "\t" << totrings * 1.0 / tottxs; - minrings = MAX_RINGS; maxrings = 0; totrings = 0; - } - if (do_hours) { - for (i=0; i<24; i++) { - std::cout << "\t" << txhr[i]; - txhr[i] = 0; - } - } - currblks = 0; - tottxs = 0; - std::cout << ENDL; + if (!(prevtm.tm_mday == 1 && currtm.tm_mday > 27)) + doprint(); } -skip: currsz += bd.size(); uint64_t coinbase_amount; uint64_t tx_fee_amount = 0; @@ -371,6 +378,8 @@ skip: if (stop_requested) break; } + if (currblks) + doprint(); core_storage->deinit(); return 0; diff --git a/src/blockchain_utilities/blockchain_usage.cpp b/src/blockchain_utilities/blockchain_usage.cpp index 129a9be21..a5228eb92 100644 --- a/src/blockchain_utilities/blockchain_usage.cpp +++ b/src/blockchain_utilities/blockchain_usage.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blockchain_utilities.h b/src/blockchain_utilities/blockchain_utilities.h index 47bbb0faf..13e2ef428 100644 --- a/src/blockchain_utilities/blockchain_utilities.h +++ b/src/blockchain_utilities/blockchain_utilities.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blocksdat_file.cpp b/src/blockchain_utilities/blocksdat_file.cpp index 606805a08..6a469289d 100644 --- a/src/blockchain_utilities/blocksdat_file.cpp +++ b/src/blockchain_utilities/blocksdat_file.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/blocksdat_file.h b/src/blockchain_utilities/blocksdat_file.h index ce224baa6..557b395a4 100644 --- a/src/blockchain_utilities/blocksdat_file.h +++ b/src/blockchain_utilities/blocksdat_file.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp index 71477912a..9375b63b0 100644 --- a/src/blockchain_utilities/bootstrap_file.cpp +++ b/src/blockchain_utilities/bootstrap_file.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -28,7 +28,6 @@ #include "bootstrap_serialization.h" #include "serialization/binary_utils.h" // dump_binary(), parse_binary() -#include "serialization/json_utils.h" // dump_json() #include "bootstrap_file.h" diff --git a/src/blockchain_utilities/bootstrap_file.h b/src/blockchain_utilities/bootstrap_file.h index 0f2776172..7a1085a56 100644 --- a/src/blockchain_utilities/bootstrap_file.h +++ b/src/blockchain_utilities/bootstrap_file.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blockchain_utilities/bootstrap_serialization.h b/src/blockchain_utilities/bootstrap_serialization.h index 261810a6b..d6ab90a99 100644 --- a/src/blockchain_utilities/bootstrap_serialization.h +++ b/src/blockchain_utilities/bootstrap_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/blocks/CMakeLists.txt b/src/blocks/CMakeLists.txt index db8fe5f94..60b27baf3 100644 --- a/src/blocks/CMakeLists.txt +++ b/src/blocks/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/checkpoints/CMakeLists.txt b/src/checkpoints/CMakeLists.txt index 665441f62..680085757 100644 --- a/src/checkpoints/CMakeLists.txt +++ b/src/checkpoints/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 330e3653c..70ae58cf0 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/checkpoints/checkpoints.h b/src/checkpoints/checkpoints.h index 07daeb4c0..7a93213c9 100644 --- a/src/checkpoints/checkpoints.h +++ b/src/checkpoints/checkpoints.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index b712ee6b1..cf38466fe 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # @@ -81,6 +81,9 @@ target_link_libraries(common PRIVATE ${OPENSSL_LIBRARIES} ${EXTRA_LIBRARIES}) +target_include_directories(common + PRIVATE + ${Boost_INCLUDE_DIRS}) #monero_install_headers(common # ${common_headers}) diff --git a/src/common/aligned.c b/src/common/aligned.c index 3e33bfa80..9ffa01f92 100644 --- a/src/common/aligned.c +++ b/src/common/aligned.c @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/aligned.h b/src/common/aligned.h index 33242a151..7c201e000 100644 --- a/src/common/aligned.h +++ b/src/common/aligned.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/common/apply_permutation.h b/src/common/apply_permutation.h index ceccdfe96..1f8fce66f 100644 --- a/src/common/apply_permutation.h +++ b/src/common/apply_permutation.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/base58.cpp b/src/common/base58.cpp index f50bd10fb..efa1d3fc8 100644 --- a/src/common/base58.cpp +++ b/src/common/base58.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/base58.h b/src/common/base58.h index fa97ab98c..7e37a57ea 100644 --- a/src/common/base58.h +++ b/src/common/base58.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/boost_serialization_helper.h b/src/common/boost_serialization_helper.h deleted file mode 100644 index 4a903107f..000000000 --- a/src/common/boost_serialization_helper.h +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) 2014-2022, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#include <boost/archive/binary_iarchive.hpp> -#include <boost/archive/portable_binary_oarchive.hpp> -#include <boost/archive/portable_binary_iarchive.hpp> -#include <boost/filesystem/operations.hpp> - - -namespace tools -{ - template<class t_object> - bool serialize_obj_to_file(t_object& obj, const std::string& file_path) - { - TRY_ENTRY(); -#if defined(_MSC_VER) - // Need to know HANDLE of file to call FlushFileBuffers - HANDLE data_file_handle = ::CreateFile(file_path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (INVALID_HANDLE_VALUE == data_file_handle) - return false; - - int data_file_descriptor = _open_osfhandle((intptr_t)data_file_handle, 0); - if (-1 == data_file_descriptor) - { - ::CloseHandle(data_file_handle); - return false; - } - - const std::unique_ptr<FILE, tools::close_file> data_file_file{_fdopen(data_file_descriptor, "wb")}; - if (nullptr == data_file_file) - { - // Call CloseHandle is not necessary - _close(data_file_descriptor); - return false; - } - - // HACK: undocumented constructor, this code may not compile - std::ofstream data_file(data_file_file.get()); - if (data_file.fail()) - { - // Call CloseHandle and _close are not necessary - return false; - } -#else - std::ofstream data_file; - data_file.open(file_path , std::ios_base::binary | std::ios_base::out| std::ios::trunc); - if (data_file.fail()) - return false; -#endif - - boost::archive::portable_binary_oarchive a(data_file); - a << obj; - if (data_file.fail()) - return false; - - data_file.flush(); -#if defined(_MSC_VER) - // To make sure the file is fully stored on disk - ::FlushFileBuffers(data_file_handle); -#endif - - return true; - CATCH_ENTRY_L0("serialize_obj_to_file", false); - } - - template<class t_object> - bool unserialize_obj_from_file(t_object& obj, const std::string& file_path) - { - TRY_ENTRY(); - - std::ifstream data_file; - data_file.open( file_path, std::ios_base::binary | std::ios_base::in); - if(data_file.fail()) - return false; - try - { - // first try reading in portable mode - boost::archive::portable_binary_iarchive a(data_file); - a >> obj; - } - catch(...) - { - // if failed, try reading in unportable mode - boost::filesystem::copy_file(file_path, file_path + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); - data_file.close(); - data_file.open( file_path, std::ios_base::binary | std::ios_base::in); - if(data_file.fail()) - return false; - boost::archive::binary_iarchive a(data_file); - a >> obj; - } - return !data_file.fail(); - CATCH_ENTRY_L0("unserialize_obj_from_file", false); - } -} diff --git a/src/common/combinator.cpp b/src/common/combinator.cpp index 72b139737..ea68f3414 100644 --- a/src/common/combinator.cpp +++ b/src/common/combinator.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/common/combinator.h b/src/common/combinator.h index 0d35e4786..6ef244a1e 100644 --- a/src/common/combinator.h +++ b/src/common/combinator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. @@ -34,6 +34,7 @@ #include <iostream> #include <vector> #include <stdexcept> +#include <cstdint> namespace tools { diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index 30ded6f33..1380622b2 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/command_line.h b/src/common/command_line.h index d80a1b0df..bd5a32edb 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/common_fwd.h b/src/common/common_fwd.h index 4853c23c9..04aa3ce0a 100644 --- a/src/common/common_fwd.h +++ b/src/common/common_fwd.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/container_helpers.h b/src/common/container_helpers.h new file mode 100644 index 000000000..5824c9c37 --- /dev/null +++ b/src/common/container_helpers.h @@ -0,0 +1,170 @@ +// Copyright (c) 2022, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Miscellaneous container helpers. + +#pragma once + +//local headers + +//third party headers + +//standard headers +#include <algorithm> +#include <unordered_map> +#include <utility> + +//forward declarations + + +namespace tools +{ + +/// convert an arbitrary function to functor +template <typename F> +inline auto as_functor(F f) +{ + return [f = std::move(f)](auto&&... args) { return f(std::forward<decltype(args)>(args)...); }; +} +/// convert a binary comparison function to a functor +/// note: for most use-cases 'const T&' will work because only non-trivial types need a user-defined comparison operation +template <typename T, typename ComparisonOpT = bool(const T&, const T&)> +inline auto compare_func(ComparisonOpT comparison_op_func) +{ + static_assert( + std::is_same< + bool, + decltype( + comparison_op_func( + std::declval<std::remove_cv_t<T>>(), + std::declval<std::remove_cv_t<T>>() + ) + ) + >::value, + "invalid callable - expected callable in form bool(T, T)" + ); + + return as_functor(std::move(comparison_op_func)); +} +/// test if a container is sorted and unique according to a comparison criteria (defaults to operator<) +/// NOTE: ComparisonOpT must establish 'strict weak ordering' https://en.cppreference.com/w/cpp/named_req/Compare +template <typename T, typename ComparisonOpT = std::less<typename T::value_type>> +bool is_sorted_and_unique(const T &container, ComparisonOpT comparison_op = ComparisonOpT{}) +{ + using ValueT = typename T::value_type; + static_assert( + std::is_same< + bool, + decltype( + comparison_op( + std::declval<std::remove_cv_t<ValueT>>(), + std::declval<std::remove_cv_t<ValueT>>() + ) + ) + >::value, + "invalid callable - expected callable in form bool(ValueT, ValueT)" + ); + + if (!std::is_sorted(container.begin(), container.end(), comparison_op)) + return false; + + if (std::adjacent_find(container.begin(), + container.end(), + [comparison_op = std::move(comparison_op)](const ValueT &a, const ValueT &b) -> bool + { + return !comparison_op(a, b) && !comparison_op(b, a); + }) + != container.end()) + return false; + + return true; +} +/// specialization for raw function pointers +template <typename T> +bool is_sorted_and_unique(const T &container, + bool (*const comparison_op_func)(const typename T::value_type&, const typename T::value_type&)) +{ + return is_sorted_and_unique(container, compare_func<typename T::value_type>(comparison_op_func)); +} +/// convenience wrapper for checking if the key to a mapped object is correct for that object +/// example: std::unorderd_map<rct::key, std::pair<rct::key, rct::xmr_amount>> where the map key is supposed to +/// reproduce the pair's rct::key; use the predicate to check that relationship +template <typename KeyT, typename ValueT, typename PredT> +bool keys_match_internal_values(const std::unordered_map<KeyT, ValueT> &map, PredT check_key_func) +{ + static_assert( + std::is_same< + bool, + decltype( + check_key_func( + std::declval<std::remove_cv_t<KeyT>>(), + std::declval<std::remove_cv_t<ValueT>>() + ) + ) + >::value, + "invalid callable - expected callable in form bool(KeyT, ValueT)" + ); + + for (const auto &map_element : map) + { + if (!check_key_func(map_element.first, map_element.second)) + return false; + } + + return true; +} +/// convenience wrapper for getting the last element after emplacing back +template <typename ContainerT> +typename ContainerT::value_type& add_element(ContainerT &container) +{ + container.emplace_back(); + return container.back(); +} +/// convenience erasor for unordered maps: std::erase_if(std::unordered_map) is C++20 +template <typename KeyT, typename ValueT, typename PredT> +void for_all_in_map_erase_if(std::unordered_map<KeyT, ValueT> &map_inout, PredT predicate) +{ + using MapValueT = typename std::unordered_map<KeyT, ValueT>::value_type; + static_assert( + std::is_same< + bool, + decltype(predicate(std::declval<std::remove_cv_t<MapValueT>>())) + >::value, + "invalid callable - expected callable in form bool(Value)" + ); + + for (auto map_it = map_inout.begin(); map_it != map_inout.end();) + { + if (predicate(*map_it)) + map_it = map_inout.erase(map_it); + else + ++map_it; + } +} + +} //namespace tools diff --git a/src/serialization/json_utils.h b/src/common/data_cache.h index 63f4bc043..5e70da115 100644 --- a/src/serialization/json_utils.h +++ b/src/common/data_cache.h @@ -28,20 +28,38 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#pragma once +#pragma once -#include <sstream> -#include "json_archive.h" +#include <unordered_set> +#include <mutex> -namespace serialization { - -template<class T> -std::string dump_json(T &v) +namespace tools { - std::stringstream ostr; - json_archive<true> oar(ostr); - assert(serialization::serialize(oar, v)); - return ostr.str(); -}; + template<typename T, size_t MAX_SIZE> + class data_cache + { + public: + void add(const T& value) + { + std::lock_guard<std::mutex> lock(m); + if (data.insert(value).second) + { + T& old_value = buf[counter++ % MAX_SIZE]; + data.erase(old_value); + old_value = value; + } + } + + bool has(const T& value) const + { + std::lock_guard<std::mutex> lock(m); + return (data.find(value) != data.end()); + } -} // namespace serialization + private: + mutable std::mutex m; + std::unordered_set<T> data; + T buf[MAX_SIZE] = {}; + size_t counter = 0; + }; +} diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index 3c1bb14f7..ce4555ae3 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -30,6 +30,8 @@ // check local first (in the event of static or in-source compilation of libunbound) #include "unbound.h" +#include <deque> +#include <set> #include <stdlib.h> #include "include_base_utils.h" #include "common/threadpool.h" @@ -102,7 +104,6 @@ get_builtin_ds(void) { static const char * const ds[] = { - ". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5\n", ". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D\n", NULL }; @@ -327,11 +328,6 @@ std::vector<std::string> DNSResolver::get_record(const std::string& url, int rec dnssec_available = false; dnssec_valid = false; - if (!check_address_syntax(url.c_str())) - { - return addresses; - } - // destructor takes care of cleanup ub_result_ptr result; @@ -415,16 +411,6 @@ DNSResolver DNSResolver::create() return DNSResolver(); } -bool DNSResolver::check_address_syntax(const char *addr) const -{ - // if string doesn't contain a dot, we won't consider it a url for now. - if (strchr(addr,'.') == NULL) - { - return false; - } - return true; -} - namespace dns_utils { @@ -522,7 +508,7 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std // send all requests in parallel std::deque<bool> avail(dns_urls.size(), false), valid(dns_urls.size(), false); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForIO(); tools::threadpool::waiter waiter(tpool); for (size_t n = 0; n < dns_urls.size(); ++n) { diff --git a/src/common/dns_utils.h b/src/common/dns_utils.h index f9507b42a..87c6ec140 100644 --- a/src/common/dns_utils.h +++ b/src/common/dns_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -159,15 +159,6 @@ private: // TODO: modify this to accommodate DNSSEC std::vector<std::string> get_record(const std::string& url, int record_type, boost::optional<std::string> (*reader)(const char *,size_t), bool& dnssec_available, bool& dnssec_valid); - /** - * @brief Checks a string to see if it looks like a URL - * - * @param addr the string to be checked - * - * @return true if it looks enough like a URL, false if not - */ - bool check_address_syntax(const char *addr) const; - DNSResolverData *m_data; }; // class DNSResolver diff --git a/src/common/download.cpp b/src/common/download.cpp index 01d4a9aab..7d23568e2 100644 --- a/src/common/download.cpp +++ b/src/common/download.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/download.h b/src/common/download.h index 52af4dc6a..ee7353089 100644 --- a/src/common/download.h +++ b/src/common/download.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/error.cpp b/src/common/error.cpp index f5e402ef4..0be5586ed 100644 --- a/src/common/error.cpp +++ b/src/common/error.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // All rights reserved. // diff --git a/src/common/error.h b/src/common/error.h index d271517f4..b79731162 100644 --- a/src/common/error.h +++ b/src/common/error.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // All rights reserved. // diff --git a/src/common/expect.h b/src/common/expect.h index 6f2d8e291..64a753f45 100644 --- a/src/common/expect.h +++ b/src/common/expect.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/common/http_connection.h b/src/common/http_connection.h index f8281fe1e..cf9a68d8d 100644 --- a/src/common/http_connection.h +++ b/src/common/http_connection.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/i18n.cpp b/src/common/i18n.cpp index 6d904a2e3..352c7879f 100644 --- a/src/common/i18n.cpp +++ b/src/common/i18n.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/i18n.h b/src/common/i18n.h index 1ed78bf8f..55506ce02 100644 --- a/src/common/i18n.h +++ b/src/common/i18n.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/json_util.h b/src/common/json_util.h index 6d2cfedb4..e512f4df8 100644 --- a/src/common/json_util.h +++ b/src/common/json_util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/notify.cpp b/src/common/notify.cpp index 28f38d8c3..e1d6ee7f5 100644 --- a/src/common/notify.cpp +++ b/src/common/notify.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/common/notify.h b/src/common/notify.h index 7ff721c8a..fa7438585 100644 --- a/src/common/notify.h +++ b/src/common/notify.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/common/password.cpp b/src/common/password.cpp index e6dff95ea..7ba4546d3 100644 --- a/src/common/password.cpp +++ b/src/common/password.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/password.h b/src/common/password.h index 26b6616ab..2d889f4e6 100644 --- a/src/common/password.h +++ b/src/common/password.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/perf_timer.cpp b/src/common/perf_timer.cpp index 30164a557..2b67154e5 100644 --- a/src/common/perf_timer.cpp +++ b/src/common/perf_timer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/perf_timer.h b/src/common/perf_timer.h index c6120b06d..31df89fee 100644 --- a/src/common/perf_timer.h +++ b/src/common/perf_timer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/pod-class.h b/src/common/pod-class.h index c6fce75e6..3cdde72a7 100644 --- a/src/common/pod-class.h +++ b/src/common/pod-class.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/pruning.cpp b/src/common/pruning.cpp index 5cae238ae..48c365425 100644 --- a/src/common/pruning.cpp +++ b/src/common/pruning.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/common/pruning.h b/src/common/pruning.h index 6530dcf2e..a0d33bf66 100644 --- a/src/common/pruning.h +++ b/src/common/pruning.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/common/rpc_client.h b/src/common/rpc_client.h index a603a7baf..f6c1ea100 100644 --- a/src/common/rpc_client.h +++ b/src/common/rpc_client.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/scoped_message_writer.h b/src/common/scoped_message_writer.h index cf4e6855f..5814d7ff5 100644 --- a/src/common/scoped_message_writer.h +++ b/src/common/scoped_message_writer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/sfinae_helpers.h b/src/common/sfinae_helpers.h index c5974fe82..fe9163c08 100644 --- a/src/common/sfinae_helpers.h +++ b/src/common/sfinae_helpers.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/spawn.cpp b/src/common/spawn.cpp index 22e840407..1bdf0ddda 100644 --- a/src/common/spawn.cpp +++ b/src/common/spawn.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/common/spawn.h b/src/common/spawn.h index f3c8dbb6e..55b2333b1 100644 --- a/src/common/spawn.h +++ b/src/common/spawn.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/common/stack_trace.cpp b/src/common/stack_trace.cpp index 130ba4d81..bef14b244 100644 --- a/src/common/stack_trace.cpp +++ b/src/common/stack_trace.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/stack_trace.h b/src/common/stack_trace.h index dc8182154..77af0c58f 100644 --- a/src/common/stack_trace.h +++ b/src/common/stack_trace.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp index 5d2acc0a6..ff6277af5 100644 --- a/src/common/threadpool.cpp +++ b/src/common/threadpool.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/threadpool.h b/src/common/threadpool.h index ce1bc5799..321f6682a 100644 --- a/src/common/threadpool.h +++ b/src/common/threadpool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -31,6 +31,7 @@ #include <boost/thread/mutex.hpp> #include <boost/thread/thread.hpp> #include <cstddef> +#include <deque> #include <functional> #include <utility> #include <vector> @@ -42,10 +43,14 @@ namespace tools class threadpool { public: - static threadpool& getInstance() { + static threadpool& getInstanceForCompute() { static threadpool instance; return instance; } + static threadpool& getInstanceForIO() { + static threadpool instance(8); + return instance; + } static threadpool *getNewForUnitTests(unsigned max_threads = 0) { return new threadpool(max_threads); } diff --git a/src/common/unordered_containers_boost_serialization.h b/src/common/unordered_containers_boost_serialization.h index bc6dd8e7a..49bbe1956 100644 --- a/src/common/unordered_containers_boost_serialization.h +++ b/src/common/unordered_containers_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/updates.cpp b/src/common/updates.cpp index 654ad068c..15efb94c1 100644 --- a/src/common/updates.cpp +++ b/src/common/updates.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/updates.h b/src/common/updates.h index 64e4eea50..da1c8353b 100644 --- a/src/common/updates.h +++ b/src/common/updates.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/utf8.h b/src/common/utf8.h index 434de15e7..d21509c9c 100644 --- a/src/common/utf8.h +++ b/src/common/utf8.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. diff --git a/src/common/util.cpp b/src/common/util.cpp index f0de73a06..a0074f44c 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -1067,7 +1067,7 @@ std::string get_nix_version_display_string() time_t tt = ts; struct tm tm; misc_utils::get_gmt_time(tt, tm); - strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%SZ", &tm); return std::string(buffer); } diff --git a/src/common/util.h b/src/common/util.h index f489594e8..05748e020 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/common/variant.h b/src/common/variant.h new file mode 100644 index 000000000..ffb34e40a --- /dev/null +++ b/src/common/variant.h @@ -0,0 +1,167 @@ +// Copyright (c) 2022, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Variant wrapper class. + +#pragma once + +//local headers + +//third party headers +#include <boost/blank.hpp> +#include <boost/mpl/begin_end.hpp> +#include <boost/mpl/distance.hpp> +#include <boost/mpl/find.hpp> +#include <boost/mpl/vector.hpp> +#include <boost/none_t.hpp> +#include <boost/variant/apply_visitor.hpp> +#include <boost/variant/get.hpp> +#include <boost/variant/static_visitor.hpp> +#include <boost/variant/variant.hpp> + +//standard headers +#include <stdexcept> +#include <type_traits> +#include <utility> + +//forward declarations + + +namespace tools +{ + +[[noreturn]] inline void variant_static_visitor_blank_err() +{ throw std::runtime_error("variant: tried to visit an empty variant."); } +[[noreturn]] inline void variant_unwrap_err() +{ throw std::runtime_error("variant: tried to access value of incorrect type."); } + +//// +// variant: convenience wrapper around boost::variant with a cleaner interface +// - the variant is 'optional' - an empty variant will evaluate to 'false' and an initialized variant will be 'true' +/// +template <typename ResultT> +struct variant_static_visitor : public boost::static_visitor<ResultT> +{ + /// provide visitation for empty variants + /// - add this to your visitor with: using variant_static_visitor::operator(); + [[noreturn]] ResultT operator()(const boost::blank) { variant_static_visitor_blank_err(); } + [[noreturn]] ResultT operator()(const boost::blank) const { variant_static_visitor_blank_err(); } +}; + +template <typename... Types> +class variant final +{ + using VType = boost::variant<boost::blank, Types...>; + +public: +//constructors + /// default constructor + variant() = default; + variant(boost::none_t) : variant{} {} //act like boost::optional + + /// construct from variant type (use enable_if to avoid issues with copy/move constructor) + template <typename T, + typename std::enable_if< + !std::is_same< + std::remove_cv_t<std::remove_reference_t<T>>, + variant<Types...> + >::value, + bool + >::type = true> + variant(T &&value) : m_value{std::forward<T>(value)} {} + +//overloaded operators + /// boolean operator: true if the variant isn't empty/uninitialized + explicit operator bool() const noexcept { return !this->is_empty(); } + +//member functions + /// check if empty/uninitialized + bool is_empty() const noexcept { return m_value.which() == 0; } + + /// check the variant type + template <typename T> + bool is_type() const noexcept { return this->index() == this->type_index_of<T>(); } + + /// try to get a handle to the embedded value (return nullptr on failure) + template <typename T> + T* try_unwrap() noexcept { return boost::strict_get< T>(&m_value); } + template <typename T> + const T* try_unwrap() const noexcept { return boost::strict_get<const T>(&m_value); } + + /// get a handle to the embedded value + template <typename T> + T& unwrap() + { + T *value_ptr{this->try_unwrap<T>()}; + if (!value_ptr) variant_unwrap_err(); + return *value_ptr; + } + template <typename T> + const T& unwrap() const + { + const T *value_ptr{this->try_unwrap<T>()}; + if (!value_ptr) variant_unwrap_err(); + return *value_ptr; + } + + /// get the type index of the currently stored type + int index() const noexcept { return m_value.which(); } + + /// get the type index of a requested type (compile error for invalid types) (boost::mp11 is boost 1.66.0) + template <typename T> + static constexpr int type_index_of() noexcept + { + using types = boost::mpl::vector<boost::blank, Types...>; + using elem = typename boost::mpl::find<types, T>::type; + using begin = typename boost::mpl::begin<types>::type; + return boost::mpl::distance<begin, elem>::value; + } + + /// check if two variants have the same type + static bool same_type(const variant<Types...> &v1, const variant<Types...> &v2) noexcept + { return v1.index() == v2.index(); } + + /// apply a visitor to the variant + template <typename VisitorT> + typename VisitorT::result_type visit(VisitorT &&visitor) + { + return boost::apply_visitor(std::forward<VisitorT>(visitor), m_value); + } + template <typename VisitorT> + typename VisitorT::result_type visit(VisitorT &&visitor) const + { + return boost::apply_visitor(std::forward<VisitorT>(visitor), m_value); + } + +private: +//member variables + /// variant of all value types + VType m_value; +}; + +} //namespace tools diff --git a/src/common/varint.h b/src/common/varint.h index 9f8b9a4ab..58d3bbb84 100644 --- a/src/common/varint.h +++ b/src/common/varint.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index 595c7f966..e17584c90 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/crypto/blake256.c b/src/crypto/blake256.c index 7e302bcad..83161fbb5 100644 --- a/src/crypto/blake256.c +++ b/src/crypto/blake256.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/blake256.h b/src/crypto/blake256.h index f727bddee..5bde2cb2f 100644 --- a/src/crypto/blake256.h +++ b/src/crypto/blake256.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/c_threads.h b/src/crypto/c_threads.h index c5431cb8d..e6c846db6 100644 --- a/src/crypto/c_threads.h +++ b/src/crypto/c_threads.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // @@ -30,29 +30,41 @@ #pragma once #ifdef _WIN32 + #include <windows.h> -#define CTHR_MUTEX_TYPE HANDLE -#define CTHR_MUTEX_INIT NULL -#define CTHR_MUTEX_LOCK(x) do { if (x == NULL) { \ - HANDLE p = CreateMutex(NULL, FALSE, NULL); \ - if (InterlockedCompareExchangePointer((PVOID*)&x, (PVOID)p, NULL) != NULL) \ - CloseHandle(p); \ - } WaitForSingleObject(x, INFINITE); } while(0) -#define CTHR_MUTEX_UNLOCK(x) ReleaseMutex(x) + +#define CTHR_RWLOCK_TYPE SRWLOCK +#define CTHR_RWLOCK_INIT SRWLOCK_INIT +#define CTHR_RWLOCK_LOCK_WRITE(x) AcquireSRWLockExclusive(&x) +#define CTHR_RWLOCK_UNLOCK_WRITE(x) ReleaseSRWLockExclusive(&x) +#define CTHR_RWLOCK_LOCK_READ(x) AcquireSRWLockShared(&x) +#define CTHR_RWLOCK_UNLOCK_READ(x) ReleaseSRWLockShared(&x) +#define CTHR_RWLOCK_TRYLOCK_READ(x) TryAcquireSRWLockShared(&x) + #define CTHR_THREAD_TYPE HANDLE -#define CTHR_THREAD_RTYPE void -#define CTHR_THREAD_RETURN return -#define CTHR_THREAD_CREATE(thr, func, arg) thr = (HANDLE)_beginthread(func, 0, arg) -#define CTHR_THREAD_JOIN(thr) WaitForSingleObject(thr, INFINITE) +#define CTHR_THREAD_RTYPE unsigned __stdcall +#define CTHR_THREAD_RETURN _endthreadex(0); return 0; +#define CTHR_THREAD_CREATE(thr, func, arg) ((thr = (HANDLE)_beginthreadex(0, 0, func, arg, 0, 0)) != 0L) +#define CTHR_THREAD_JOIN(thr) do { WaitForSingleObject(thr, INFINITE); CloseHandle(thr); } while(0) +#define CTHR_THREAD_CLOSE(thr) CloseHandle((HANDLE)thr); + #else + #include <pthread.h> -#define CTHR_MUTEX_TYPE pthread_mutex_t -#define CTHR_MUTEX_INIT PTHREAD_MUTEX_INITIALIZER -#define CTHR_MUTEX_LOCK(x) pthread_mutex_lock(&x) -#define CTHR_MUTEX_UNLOCK(x) pthread_mutex_unlock(&x) + +#define CTHR_RWLOCK_TYPE pthread_rwlock_t +#define CTHR_RWLOCK_INIT PTHREAD_RWLOCK_INITIALIZER +#define CTHR_RWLOCK_LOCK_WRITE(x) pthread_rwlock_wrlock(&x) +#define CTHR_RWLOCK_UNLOCK_WRITE(x) pthread_rwlock_unlock(&x) +#define CTHR_RWLOCK_LOCK_READ(x) pthread_rwlock_rdlock(&x) +#define CTHR_RWLOCK_UNLOCK_READ(x) pthread_rwlock_unlock(&x) +#define CTHR_RWLOCK_TRYLOCK_READ(x) (pthread_rwlock_tryrdlock(&x) == 0) + #define CTHR_THREAD_TYPE pthread_t #define CTHR_THREAD_RTYPE void * #define CTHR_THREAD_RETURN return NULL -#define CTHR_THREAD_CREATE(thr, func, arg) pthread_create(&thr, NULL, func, arg) +#define CTHR_THREAD_CREATE(thr, func, arg) (pthread_create(&thr, NULL, func, arg) == 0) #define CTHR_THREAD_JOIN(thr) pthread_join(thr, NULL) +#define CTHR_THREAD_CLOSE(thr) + #endif diff --git a/src/crypto/chacha.h b/src/crypto/chacha.h index 74f05cbe8..80170dea6 100644 --- a/src/crypto/chacha.h +++ b/src/crypto/chacha.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto-ops-data.c b/src/crypto/crypto-ops-data.c index 1a85de60d..e31cd6b9c 100644 --- a/src/crypto/crypto-ops-data.c +++ b/src/crypto/crypto-ops-data.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index 4b392d472..e9a5cc6f6 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -38,7 +38,6 @@ DISABLE_VS_WARNINGS(4146 4244) /* Predeclarations */ -static void fe_mul(fe, const fe, const fe); static void fe_sq(fe, const fe); static void ge_madd(ge_p1p1 *, const ge_p3 *, const ge_precomp *); static void ge_msub(ge_p1p1 *, const ge_p3 *, const ge_precomp *); @@ -72,7 +71,7 @@ uint64_t load_4(const unsigned char *in) h = 0 */ -static void fe_0(fe h) { +void fe_0(fe h) { h[0] = 0; h[1] = 0; h[2] = 0; @@ -375,7 +374,7 @@ Can get away with 11 carries, but then data flow is much deeper. With tighter constraints on inputs can squeeze carries into int32. */ -static void fe_mul(fe h, const fe f, const fe g) { +void fe_mul(fe h, const fe f, const fe g) { int32_t f0 = f[0]; int32_t f1 = f[1]; int32_t f2 = f[2]; diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index e4901e080..49bc8e081 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -30,6 +30,8 @@ #pragma once +#include <stdint.h> + /* From fe.h */ typedef int32_t fe[10]; @@ -161,5 +163,7 @@ void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); void fe_add(fe h, const fe f, const fe g); void fe_tobytes(unsigned char *, const fe); void fe_invert(fe out, const fe z); +void fe_mul(fe out, const fe, const fe); +void fe_0(fe h); int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p); diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 77a36069a..0ab4cad36 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index d8cd6c6a0..81d6e1803 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -335,8 +335,16 @@ namespace crypto { inline bool operator<(const public_key &p1, const public_key &p2) { return memcmp(&p1, &p2, sizeof(public_key)) < 0; } inline bool operator>(const public_key &p1, const public_key &p2) { return p2 < p1; } + inline bool operator<(const key_image &p1, const key_image &p2) { return memcmp(&p1, &p2, sizeof(key_image)) < 0; } + inline bool operator>(const key_image &p1, const key_image &p2) { return p2 < p1; } } +// type conversions for easier calls to sc_add(), sc_sub(), hash functions +inline unsigned char* to_bytes(crypto::ec_scalar &scalar) { return &reinterpret_cast<unsigned char&>(scalar); } +inline const unsigned char* to_bytes(const crypto::ec_scalar &scalar) { return &reinterpret_cast<const unsigned char&>(scalar); } +inline unsigned char* to_bytes(crypto::ec_point &point) { return &reinterpret_cast<unsigned char&>(point); } +inline const unsigned char* to_bytes(const crypto::ec_point &point) { return &reinterpret_cast<const unsigned char&>(point); } + CRYPTO_MAKE_HASHABLE(public_key) CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key) CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(public_key_memsafe) diff --git a/src/crypto/crypto_ops_builder/README.md b/src/crypto/crypto_ops_builder/README.md index 831c6a63c..aae9e88cd 100644 --- a/src/crypto/crypto_ops_builder/README.md +++ b/src/crypto/crypto_ops_builder/README.md @@ -1,6 +1,6 @@ # Monero -Copyright (c) 2014-2022, The Monero Project +Copyright (c) 2014-2023, The Monero Project ## Crypto Ops Builder diff --git a/src/crypto/crypto_ops_builder/crypto-ops-data.c b/src/crypto/crypto_ops_builder/crypto-ops-data.c index 4785f975f..ac1cf82e5 100644 --- a/src/crypto/crypto_ops_builder/crypto-ops-data.c +++ b/src/crypto/crypto_ops_builder/crypto-ops-data.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/crypto-ops-old.c b/src/crypto/crypto_ops_builder/crypto-ops-old.c index 5d632809e..10c25bd1e 100644 --- a/src/crypto/crypto_ops_builder/crypto-ops-old.c +++ b/src/crypto/crypto_ops_builder/crypto-ops-old.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/crypto-ops.h b/src/crypto/crypto_ops_builder/crypto-ops.h index 568bf2a37..171343676 100644 --- a/src/crypto/crypto_ops_builder/crypto-ops.h +++ b/src/crypto/crypto_ops_builder/crypto-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py b/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py index 16b6c0ba9..5bb88f0a9 100644 --- a/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py +++ b/src/crypto/crypto_ops_builder/ref10CommentedCombined/MakeCryptoOps.py @@ -15,7 +15,7 @@ print("maybe someone smart can replace the sed with perl..") a = "" license = textwrap.dedent("""\ - // Copyright (c) 2014-2022, The Monero Project + // Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h b/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h index 8c0cbcda1..4badb59d6 100644 --- a/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h +++ b/src/crypto/crypto_ops_builder/ref10CommentedCombined/crypto-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/duration.h b/src/crypto/duration.h index 25d1c0b8c..ad9f0a8c4 100644 --- a/src/crypto/duration.h +++ b/src/crypto/duration.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/crypto/generic-ops.h b/src/crypto/generic-ops.h index 5a5e09f9b..da19157f6 100644 --- a/src/crypto/generic-ops.h +++ b/src/crypto/generic-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/groestl.h b/src/crypto/groestl.h index 899660cb1..6fb0a26f4 100644 --- a/src/crypto/groestl.h +++ b/src/crypto/groestl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/groestl_tables.h b/src/crypto/groestl_tables.h index 556354c47..c553b98f5 100644 --- a/src/crypto/groestl_tables.h +++ b/src/crypto/groestl_tables.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-blake.c b/src/crypto/hash-extra-blake.c index 1557269e6..f3e91a0a8 100644 --- a/src/crypto/hash-extra-blake.c +++ b/src/crypto/hash-extra-blake.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-groestl.c b/src/crypto/hash-extra-groestl.c index 96230aed7..7d8e84eaa 100644 --- a/src/crypto/hash-extra-groestl.c +++ b/src/crypto/hash-extra-groestl.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash-extra-jh.c b/src/crypto/hash-extra-jh.c index 4d7481c07..f2180a5dc 100644 --- a/src/crypto/hash-extra-jh.c +++ b/src/crypto/hash-extra-jh.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -36,7 +36,9 @@ #include "jh.h" #include "hash-ops.h" +#define JH_HASH_BITLEN HASH_SIZE * 8 + void hash_extra_jh(const void *data, size_t length, char *hash) { - int r = jh_hash(HASH_SIZE * 8, data, 8 * length, (uint8_t*)hash); - assert(SUCCESS == r); + // No need to check for failure b/c jh_hash only fails for invalid hash size + jh_hash(JH_HASH_BITLEN, data, 8 * length, (uint8_t*)hash); } diff --git a/src/crypto/hash-extra-skein.c b/src/crypto/hash-extra-skein.c index 9ea9c4faa..07bfdccd0 100644 --- a/src/crypto/hash-extra-skein.c +++ b/src/crypto/hash-extra-skein.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -34,7 +34,9 @@ #include "hash-ops.h" #include "skein.h" +#define SKEIN_HASH_BITLEN HASH_SIZE * 8 + void hash_extra_skein(const void *data, size_t length, char *hash) { - int r = skein_hash(8 * HASH_SIZE, data, 8 * length, (uint8_t*)hash); - assert(SKEIN_SUCCESS == r); + // No need to check for failure b/c skein_hash only fails for invalid hash size + skein_hash(SKEIN_HASH_BITLEN, data, 8 * length, (uint8_t*)hash); } diff --git a/src/crypto/hash-ops.h b/src/crypto/hash-ops.h index b7ec80d7c..83cc78ce2 100644 --- a/src/crypto/hash-ops.h +++ b/src/crypto/hash-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -97,5 +97,9 @@ void rx_slow_hash_allocate_state(void); void rx_slow_hash_free_state(void); uint64_t rx_seedheight(const uint64_t height); void rx_seedheights(const uint64_t height, uint64_t *seed_height, uint64_t *next_height); -void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const char *seedhash, const void *data, size_t length, char *hash, int miners, int is_alt); -void rx_reorg(const uint64_t split_height); + +void rx_set_main_seedhash(const char *seedhash, size_t max_dataset_init_threads); +void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *result_hash); + +void rx_set_miner_thread(uint32_t value, size_t max_dataset_init_threads); +uint32_t rx_get_miner_thread(void); diff --git a/src/crypto/hash.c b/src/crypto/hash.c index 7c761a1b9..b73a8587a 100644 --- a/src/crypto/hash.c +++ b/src/crypto/hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hash.h b/src/crypto/hash.h index 2812422e0..c1fccc50c 100644 --- a/src/crypto/hash.h +++ b/src/crypto/hash.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hmac-keccak.c b/src/crypto/hmac-keccak.c index 771fcc27e..0bdbf6d0f 100644 --- a/src/crypto/hmac-keccak.c +++ b/src/crypto/hmac-keccak.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/hmac-keccak.h b/src/crypto/hmac-keccak.h index 6b3633617..9d92a693a 100644 --- a/src/crypto/hmac-keccak.h +++ b/src/crypto/hmac-keccak.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/initializer.h b/src/crypto/initializer.h index 90c09a087..57c15d3e7 100644 --- a/src/crypto/initializer.h +++ b/src/crypto/initializer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c index f098cbdf0..6616d3530 100644 --- a/src/crypto/keccak.c +++ b/src/crypto/keccak.c @@ -123,7 +123,7 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) size_t i, rsiz, rsizw; static_assert(HASH_DATA_AREA <= sizeof(temp), "Bad keccak preconditions"); - if (mdlen <= 0 || (mdlen > 100 && sizeof(st) != (size_t)mdlen)) + if (mdlen <= 0 || (mdlen >= 100 && sizeof(st) != (size_t)mdlen)) { local_abort("Bad keccak use"); } diff --git a/src/crypto/random.c b/src/crypto/random.c index cfb637fb4..854b29547 100644 --- a/src/crypto/random.c +++ b/src/crypto/random.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/random.h b/src/crypto/random.h index d50f29430..5bb80728a 100644 --- a/src/crypto/random.h +++ b/src/crypto/random.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/rx-slow-hash.c b/src/crypto/rx-slow-hash.c index 40ef96ac9..4ba8f0561 100644 --- a/src/crypto/rx-slow-hash.c +++ b/src/crypto/rx-slow-hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // @@ -43,32 +43,41 @@ #define RX_LOGCAT "randomx" +// Report large page allocation failures as debug messages +#define alloc_err_msg(x) mdebug(RX_LOGCAT, x); + +static CTHR_RWLOCK_TYPE main_dataset_lock = CTHR_RWLOCK_INIT; +static CTHR_RWLOCK_TYPE main_cache_lock = CTHR_RWLOCK_INIT; + +static randomx_dataset *main_dataset = NULL; +static randomx_cache *main_cache = NULL; +static char main_seedhash[HASH_SIZE]; +static int main_seedhash_set = 0; + +static CTHR_RWLOCK_TYPE secondary_cache_lock = CTHR_RWLOCK_INIT; + +static randomx_cache *secondary_cache = NULL; +static char secondary_seedhash[HASH_SIZE]; +static int secondary_seedhash_set = 0; + #if defined(_MSC_VER) #define THREADV __declspec(thread) #else #define THREADV __thread #endif -typedef struct rx_state { - CTHR_MUTEX_TYPE rs_mutex; - char rs_hash[HASH_SIZE]; - uint64_t rs_height; - randomx_cache *rs_cache; -} rx_state; - -static CTHR_MUTEX_TYPE rx_mutex = CTHR_MUTEX_INIT; -static CTHR_MUTEX_TYPE rx_dataset_mutex = CTHR_MUTEX_INIT; +static THREADV randomx_vm *main_vm_full = NULL; +static THREADV randomx_vm *main_vm_light = NULL; +static THREADV randomx_vm *secondary_vm_light = NULL; -static rx_state rx_s[2] = {{CTHR_MUTEX_INIT,{0},0,0},{CTHR_MUTEX_INIT,{0},0,0}}; +static THREADV uint32_t miner_thread = 0; -static randomx_dataset *rx_dataset; -static int rx_dataset_nomem; -static int rx_dataset_nolp; -static uint64_t rx_dataset_height; -static THREADV randomx_vm *rx_vm = NULL; +static bool is_main(const char* seedhash) { return main_seedhash_set && (memcmp(seedhash, main_seedhash, HASH_SIZE) == 0); } +static bool is_secondary(const char* seedhash) { return secondary_seedhash_set && (memcmp(seedhash, secondary_seedhash, HASH_SIZE) == 0); } static void local_abort(const char *msg) { + merror(RX_LOGCAT, "%s", msg); fprintf(stderr, "%s\n", msg); #ifdef NDEBUG _exit(1); @@ -77,6 +86,16 @@ static void local_abort(const char *msg) #endif } +static void hash2hex(const char* hash, char* hex) { + const char* d = "0123456789abcdef"; + for (int i = 0; i < HASH_SIZE; ++i) { + const uint8_t b = hash[i]; + hex[i * 2 + 0] = d[b >> 4]; + hex[i * 2 + 1] = d[b & 15]; + } + hex[HASH_SIZE * 2] = '\0'; +} + static inline int disabled_flags(void) { static int flags = -1; @@ -157,19 +176,6 @@ static unsigned int get_seedhash_epoch_blocks(void) return blocks; } -void rx_reorg(const uint64_t split_height) { - int i; - CTHR_MUTEX_LOCK(rx_mutex); - for (i=0; i<2; i++) { - if (split_height <= rx_s[i].rs_height) { - if (rx_s[i].rs_height == rx_dataset_height) - rx_dataset_height = 1; - rx_s[i].rs_height = 1; /* set to an invalid seed height */ - } - } - CTHR_MUTEX_UNLOCK(rx_mutex); -} - uint64_t rx_seedheight(const uint64_t height) { const uint64_t seedhash_epoch_lag = get_seedhash_epoch_lag(); const uint64_t seedhash_epoch_blocks = get_seedhash_epoch_blocks(); @@ -183,6 +189,103 @@ void rx_seedheights(const uint64_t height, uint64_t *seedheight, uint64_t *nexth *nextheight = rx_seedheight(height + get_seedhash_epoch_lag()); } +static void rx_alloc_dataset(randomx_flags flags, randomx_dataset** dataset, int ignore_env) +{ + if (*dataset) { + return; + } + + if (disabled_flags() & RANDOMX_FLAG_FULL_MEM) { + static int shown = 0; + if (!shown) { + shown = 1; + minfo(RX_LOGCAT, "RandomX dataset is disabled by MONERO_RANDOMX_UMASK environment variable."); + } + return; + } + + if (!ignore_env && !getenv("MONERO_RANDOMX_FULL_MEM")) { + static int shown = 0; + if (!shown) { + shown = 1; + minfo(RX_LOGCAT, "RandomX dataset is not enabled by default. Use MONERO_RANDOMX_FULL_MEM environment variable to enable it."); + } + return; + } + + *dataset = randomx_alloc_dataset((flags | RANDOMX_FLAG_LARGE_PAGES) & ~disabled_flags()); + if (!*dataset) { + alloc_err_msg("Couldn't allocate RandomX dataset using large pages"); + *dataset = randomx_alloc_dataset(flags & ~disabled_flags()); + if (!*dataset) { + merror(RX_LOGCAT, "Couldn't allocate RandomX dataset"); + } + } +} + +static void rx_alloc_cache(randomx_flags flags, randomx_cache** cache) +{ + if (*cache) { + return; + } + + *cache = randomx_alloc_cache((flags | RANDOMX_FLAG_LARGE_PAGES) & ~disabled_flags()); + if (!*cache) { + alloc_err_msg("Couldn't allocate RandomX cache using large pages"); + *cache = randomx_alloc_cache(flags & ~disabled_flags()); + if (!*cache) local_abort("Couldn't allocate RandomX cache"); + } +} + +static void rx_init_full_vm(randomx_flags flags, randomx_vm** vm) +{ + if (*vm || !main_dataset || (disabled_flags() & RANDOMX_FLAG_FULL_MEM)) { + return; + } + + if ((flags & RANDOMX_FLAG_JIT) && !miner_thread) { + flags |= RANDOMX_FLAG_SECURE; + } + + *vm = randomx_create_vm((flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_FULL_MEM) & ~disabled_flags(), NULL, main_dataset); + if (!*vm) { + static int shown = 0; + if (!shown) { + shown = 1; + alloc_err_msg("Couldn't allocate RandomX full VM using large pages (will print only once)"); + } + *vm = randomx_create_vm((flags | RANDOMX_FLAG_FULL_MEM) & ~disabled_flags(), NULL, main_dataset); + if (!*vm) { + merror(RX_LOGCAT, "Couldn't allocate RandomX full VM"); + } + } +} + +static void rx_init_light_vm(randomx_flags flags, randomx_vm** vm, randomx_cache* cache) +{ + if (*vm) { + randomx_vm_set_cache(*vm, cache); + return; + } + + if ((flags & RANDOMX_FLAG_JIT) && !miner_thread) { + flags |= RANDOMX_FLAG_SECURE; + } + + flags &= ~RANDOMX_FLAG_FULL_MEM; + + *vm = randomx_create_vm((flags | RANDOMX_FLAG_LARGE_PAGES) & ~disabled_flags(), cache, NULL); + if (!*vm) { + static int shown = 0; + if (!shown) { + shown = 1; + alloc_err_msg("Couldn't allocate RandomX light VM using large pages (will print only once)"); + } + *vm = randomx_create_vm(flags & ~disabled_flags(), cache, NULL); + if (!*vm) local_abort("Couldn't allocate RandomX light VM"); + } +} + typedef struct seedinfo { randomx_cache *si_cache; unsigned long si_start; @@ -191,187 +294,231 @@ typedef struct seedinfo { static CTHR_THREAD_RTYPE rx_seedthread(void *arg) { seedinfo *si = arg; - randomx_init_dataset(rx_dataset, si->si_cache, si->si_start, si->si_count); + randomx_init_dataset(main_dataset, si->si_cache, si->si_start, si->si_count); CTHR_THREAD_RETURN; } -static void rx_initdata(randomx_cache *rs_cache, const int miners, const uint64_t seedheight) { - if (miners > 1) { - unsigned long delta = randomx_dataset_item_count() / miners; - unsigned long start = 0; - int i; - seedinfo *si; - CTHR_THREAD_TYPE *st; - si = malloc(miners * sizeof(seedinfo)); - if (si == NULL) - local_abort("Couldn't allocate RandomX mining threadinfo"); - st = malloc(miners * sizeof(CTHR_THREAD_TYPE)); - if (st == NULL) { - free(si); - local_abort("Couldn't allocate RandomX mining threadlist"); - } - for (i=0; i<miners-1; i++) { - si[i].si_cache = rs_cache; - si[i].si_start = start; - si[i].si_count = delta; - start += delta; - } - si[i].si_cache = rs_cache; +static void rx_init_dataset(size_t max_threads) { + if (!main_dataset) { + return; + } + + // leave 2 CPU cores for other tasks + const size_t num_threads = (max_threads < 4) ? 1 : (max_threads - 2); + seedinfo* si = malloc(num_threads * sizeof(seedinfo)); + if (!si) local_abort("Couldn't allocate RandomX mining threadinfo"); + + const uint32_t delta = randomx_dataset_item_count() / num_threads; + uint32_t start = 0; + + const size_t n1 = num_threads - 1; + for (size_t i = 0; i < n1; ++i) { + si[i].si_cache = main_cache; si[i].si_start = start; - si[i].si_count = randomx_dataset_item_count() - start; - for (i=1; i<miners; i++) { - CTHR_THREAD_CREATE(st[i], rx_seedthread, &si[i]); - } - randomx_init_dataset(rx_dataset, rs_cache, 0, si[0].si_count); - for (i=1; i<miners; i++) { - CTHR_THREAD_JOIN(st[i]); + si[i].si_count = delta; + start += delta; + } + + si[n1].si_cache = main_cache; + si[n1].si_start = start; + si[n1].si_count = randomx_dataset_item_count() - start; + + CTHR_THREAD_TYPE *st = malloc(num_threads * sizeof(CTHR_THREAD_TYPE)); + if (!st) local_abort("Couldn't allocate RandomX mining threadlist"); + + CTHR_RWLOCK_LOCK_READ(main_cache_lock); + for (size_t i = 0; i < n1; ++i) { + if (!CTHR_THREAD_CREATE(st[i], rx_seedthread, &si[i])) { + local_abort("Couldn't start RandomX seed thread"); } - free(st); - free(si); - } else { - randomx_init_dataset(rx_dataset, rs_cache, 0, randomx_dataset_item_count()); } - rx_dataset_height = seedheight; + randomx_init_dataset(main_dataset, si[n1].si_cache, si[n1].si_start, si[n1].si_count); + for (size_t i = 0; i < n1; ++i) CTHR_THREAD_JOIN(st[i]); + CTHR_RWLOCK_UNLOCK_READ(main_cache_lock); + + free(st); + free(si); + + minfo(RX_LOGCAT, "RandomX dataset initialized"); } -void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const char *seedhash, const void *data, size_t length, - char *hash, int miners, int is_alt) { - uint64_t s_height = rx_seedheight(mainheight); - int toggle = (s_height & get_seedhash_epoch_blocks()) != 0; - randomx_flags flags = enabled_flags() & ~disabled_flags(); - rx_state *rx_sp; - randomx_cache *cache; - - CTHR_MUTEX_LOCK(rx_mutex); - - /* if alt block but with same seed as mainchain, no need for alt cache */ - if (is_alt) { - if (s_height == seedheight && !memcmp(rx_s[toggle].rs_hash, seedhash, HASH_SIZE)) - is_alt = 0; - } else { - /* RPC could request an earlier block on mainchain */ - if (s_height > seedheight) - is_alt = 1; - /* miner can be ahead of mainchain */ - else if (s_height < seedheight) - toggle ^= 1; - } - - toggle ^= (is_alt != 0); - - rx_sp = &rx_s[toggle]; - CTHR_MUTEX_LOCK(rx_sp->rs_mutex); - CTHR_MUTEX_UNLOCK(rx_mutex); - - cache = rx_sp->rs_cache; - if (cache == NULL) { - if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) { - cache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES); - if (cache == NULL) { - mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX cache"); - } - } - if (cache == NULL) { - cache = randomx_alloc_cache(flags); - if (cache == NULL) - local_abort("Couldn't allocate RandomX cache"); - } +typedef struct thread_info { + char seedhash[HASH_SIZE]; + size_t max_threads; +} thread_info; + +static CTHR_THREAD_RTYPE rx_set_main_seedhash_thread(void *arg) { + thread_info* info = arg; + + CTHR_RWLOCK_LOCK_WRITE(main_dataset_lock); + CTHR_RWLOCK_LOCK_WRITE(main_cache_lock); + + // Double check that seedhash wasn't already updated + if (is_main(info->seedhash)) { + CTHR_RWLOCK_UNLOCK_WRITE(main_cache_lock); + CTHR_RWLOCK_UNLOCK_WRITE(main_dataset_lock); + free(info); + CTHR_THREAD_RETURN; } - if (rx_sp->rs_height != seedheight || rx_sp->rs_cache == NULL || memcmp(seedhash, rx_sp->rs_hash, HASH_SIZE)) { - randomx_init_cache(cache, seedhash, HASH_SIZE); - rx_sp->rs_cache = cache; - rx_sp->rs_height = seedheight; - memcpy(rx_sp->rs_hash, seedhash, HASH_SIZE); + memcpy(main_seedhash, info->seedhash, HASH_SIZE); + main_seedhash_set = 1; + + char buf[HASH_SIZE * 2 + 1]; + hash2hex(main_seedhash, buf); + minfo(RX_LOGCAT, "RandomX new main seed hash is %s", buf); + + const randomx_flags flags = enabled_flags() & ~disabled_flags(); + rx_alloc_dataset(flags, &main_dataset, 0); + rx_alloc_cache(flags, &main_cache); + + randomx_init_cache(main_cache, info->seedhash, HASH_SIZE); + minfo(RX_LOGCAT, "RandomX main cache initialized"); + + CTHR_RWLOCK_UNLOCK_WRITE(main_cache_lock); + + // From this point, rx_slow_hash can calculate hashes in light mode, but dataset is not initialized yet + rx_init_dataset(info->max_threads); + + CTHR_RWLOCK_UNLOCK_WRITE(main_dataset_lock); + + free(info); + CTHR_THREAD_RETURN; +} + +void rx_set_main_seedhash(const char *seedhash, size_t max_dataset_init_threads) { + // Early out if seedhash didn't change + if (is_main(seedhash)) { + return; } - if (rx_vm == NULL) { - if ((flags & RANDOMX_FLAG_JIT) && !miners) { - flags |= RANDOMX_FLAG_SECURE & ~disabled_flags(); - } - if (miners && (disabled_flags() & RANDOMX_FLAG_FULL_MEM)) { - miners = 0; - } - if (miners) { - CTHR_MUTEX_LOCK(rx_dataset_mutex); - if (!rx_dataset_nomem) { - if (rx_dataset == NULL) { - if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) { - rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_LARGE_PAGES); - if (rx_dataset == NULL) { - mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX dataset"); - } - } - if (rx_dataset == NULL) - rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT); - if (rx_dataset != NULL) - rx_initdata(rx_sp->rs_cache, miners, seedheight); - } - } - if (rx_dataset != NULL) - flags |= RANDOMX_FLAG_FULL_MEM; - else { - miners = 0; - if (!rx_dataset_nomem) { - rx_dataset_nomem = 1; - mwarning(RX_LOGCAT, "Couldn't allocate RandomX dataset for miner"); + + // Update main cache and dataset in the background + thread_info* info = malloc(sizeof(thread_info)); + if (!info) local_abort("Couldn't allocate RandomX mining threadinfo"); + + memcpy(info->seedhash, seedhash, HASH_SIZE); + info->max_threads = max_dataset_init_threads; + + CTHR_THREAD_TYPE t; + if (!CTHR_THREAD_CREATE(t, rx_set_main_seedhash_thread, info)) { + local_abort("Couldn't start RandomX seed thread"); + } + CTHR_THREAD_CLOSE(t); +} + +void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *result_hash) { + const randomx_flags flags = enabled_flags() & ~disabled_flags(); + int success = 0; + + // Fast path (seedhash == main_seedhash) + // Multiple threads can run in parallel in fast or light mode, 1-2 ms or 10-15 ms per hash per thread + if (is_main(seedhash)) { + // If CTHR_RWLOCK_TRYLOCK_READ fails it means dataset is being initialized now, so use the light mode + if (main_dataset && CTHR_RWLOCK_TRYLOCK_READ(main_dataset_lock)) { + // Double check that main_seedhash didn't change + if (is_main(seedhash)) { + rx_init_full_vm(flags, &main_vm_full); + if (main_vm_full) { + randomx_calculate_hash(main_vm_full, data, length, result_hash); + success = 1; } } - CTHR_MUTEX_UNLOCK(rx_dataset_mutex); - } - if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES) && !rx_dataset_nolp) { - rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, rx_dataset); - if(rx_vm == NULL) { //large pages failed - mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX VM"); - rx_dataset_nolp = 1; + CTHR_RWLOCK_UNLOCK_READ(main_dataset_lock); + } else { + CTHR_RWLOCK_LOCK_READ(main_cache_lock); + // Double check that main_seedhash didn't change + if (is_main(seedhash)) { + rx_init_light_vm(flags, &main_vm_light, main_cache); + randomx_calculate_hash(main_vm_light, data, length, result_hash); + success = 1; } + CTHR_RWLOCK_UNLOCK_READ(main_cache_lock); } - if (rx_vm == NULL) - rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset); - if(rx_vm == NULL) {//fallback if everything fails - flags = RANDOMX_FLAG_DEFAULT | (miners ? RANDOMX_FLAG_FULL_MEM : 0); - rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset); - } - if (rx_vm == NULL) - local_abort("Couldn't allocate RandomX VM"); - } else if (miners) { - CTHR_MUTEX_LOCK(rx_dataset_mutex); - if (rx_dataset != NULL && rx_dataset_height != seedheight) - rx_initdata(cache, miners, seedheight); - else if (rx_dataset == NULL) { - /* this is a no-op if the cache hasn't changed */ - randomx_vm_set_cache(rx_vm, rx_sp->rs_cache); + } + + if (success) { + return; + } + + char buf[HASH_SIZE * 2 + 1]; + + // Slow path (seedhash != main_seedhash, but seedhash == secondary_seedhash) + // Multiple threads can run in parallel in light mode, 10-15 ms per hash per thread + if (!secondary_cache) { + CTHR_RWLOCK_LOCK_WRITE(secondary_cache_lock); + if (!secondary_cache) { + hash2hex(seedhash, buf); + minfo(RX_LOGCAT, "RandomX new secondary seed hash is %s", buf); + + rx_alloc_cache(flags, &secondary_cache); + randomx_init_cache(secondary_cache, seedhash, HASH_SIZE); + minfo(RX_LOGCAT, "RandomX secondary cache updated"); + memcpy(secondary_seedhash, seedhash, HASH_SIZE); + secondary_seedhash_set = 1; } - CTHR_MUTEX_UNLOCK(rx_dataset_mutex); - } else { - /* this is a no-op if the cache hasn't changed */ - randomx_vm_set_cache(rx_vm, rx_sp->rs_cache); - } - /* mainchain users can run in parallel */ - if (!is_alt) - CTHR_MUTEX_UNLOCK(rx_sp->rs_mutex); - randomx_calculate_hash(rx_vm, data, length, hash); - /* altchain slot users always get fully serialized */ - if (is_alt) - CTHR_MUTEX_UNLOCK(rx_sp->rs_mutex); -} + CTHR_RWLOCK_UNLOCK_WRITE(secondary_cache_lock); + } + + CTHR_RWLOCK_LOCK_READ(secondary_cache_lock); + if (is_secondary(seedhash)) { + rx_init_light_vm(flags, &secondary_vm_light, secondary_cache); + randomx_calculate_hash(secondary_vm_light, data, length, result_hash); + success = 1; + } + CTHR_RWLOCK_UNLOCK_READ(secondary_cache_lock); -void rx_slow_hash_allocate_state(void) { + if (success) { + return; + } + + // Slowest path (seedhash != main_seedhash, seedhash != secondary_seedhash) + // Only one thread runs at a time and updates secondary_seedhash if needed, up to 200-500 ms per hash + CTHR_RWLOCK_LOCK_WRITE(secondary_cache_lock); + if (!is_secondary(seedhash)) { + hash2hex(seedhash, buf); + minfo(RX_LOGCAT, "RandomX new secondary seed hash is %s", buf); + + randomx_init_cache(secondary_cache, seedhash, HASH_SIZE); + minfo(RX_LOGCAT, "RandomX secondary cache updated"); + memcpy(secondary_seedhash, seedhash, HASH_SIZE); + secondary_seedhash_set = 1; + } + rx_init_light_vm(flags, &secondary_vm_light, secondary_cache); + randomx_calculate_hash(secondary_vm_light, data, length, result_hash); + CTHR_RWLOCK_UNLOCK_WRITE(secondary_cache_lock); } -void rx_slow_hash_free_state(void) { - if (rx_vm != NULL) { - randomx_destroy_vm(rx_vm); - rx_vm = NULL; +void rx_set_miner_thread(uint32_t value, size_t max_dataset_init_threads) { + miner_thread = value; + + // If dataset is not allocated yet, try to allocate and initialize it + CTHR_RWLOCK_LOCK_WRITE(main_dataset_lock); + if (main_dataset) { + CTHR_RWLOCK_UNLOCK_WRITE(main_dataset_lock); + return; } + + const randomx_flags flags = enabled_flags() & ~disabled_flags(); + rx_alloc_dataset(flags, &main_dataset, 1); + rx_init_dataset(max_dataset_init_threads); + + CTHR_RWLOCK_UNLOCK_WRITE(main_dataset_lock); +} + +uint32_t rx_get_miner_thread() { + return miner_thread; } -void rx_stop_mining(void) { - CTHR_MUTEX_LOCK(rx_dataset_mutex); - if (rx_dataset != NULL) { - randomx_dataset *rd = rx_dataset; - rx_dataset = NULL; - randomx_release_dataset(rd); +void rx_slow_hash_allocate_state() {} + +static void rx_destroy_vm(randomx_vm** vm) { + if (*vm) { + randomx_destroy_vm(*vm); + *vm = NULL; } - rx_dataset_nomem = 0; - rx_dataset_nolp = 0; - CTHR_MUTEX_UNLOCK(rx_dataset_mutex); +} + +void rx_slow_hash_free_state() { + rx_destroy_vm(&main_vm_full); + rx_destroy_vm(&main_vm_light); + rx_destroy_vm(&secondary_vm_light); } diff --git a/src/crypto/skein_port.h b/src/crypto/skein_port.h index 2b701e8cc..6ba83d3d7 100644 --- a/src/crypto/skein_port.h +++ b/src/crypto/skein_port.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c index 0de7db505..6b975f627 100644 --- a/src/crypto/slow-hash.c +++ b/src/crypto/slow-hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/tree-hash.c b/src/crypto/tree-hash.c index 93a1bce4d..0959b5050 100644 --- a/src/crypto/tree-hash.c +++ b/src/crypto/tree-hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/crypto/wallet/CMakeLists.txt b/src/crypto/wallet/CMakeLists.txt index ac1bdf7fd..e724b3e96 100644 --- a/src/crypto/wallet/CMakeLists.txt +++ b/src/crypto/wallet/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2022, The Monero Project +# Copyright (c) 2020-2023, The Monero Project # # All rights reserved. diff --git a/src/crypto/wallet/crypto.h b/src/crypto/wallet/crypto.h index cee0ca18e..27da956ec 100644 --- a/src/crypto/wallet/crypto.h +++ b/src/crypto/wallet/crypto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/crypto/wallet/empty.h.in b/src/crypto/wallet/empty.h.in index b884a57b5..bf54f54c5 100644 --- a/src/crypto/wallet/empty.h.in +++ b/src/crypto/wallet/empty.h.in @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt index 1414be1b2..864306cf7 100644 --- a/src/cryptonote_basic/CMakeLists.txt +++ b/src/cryptonote_basic/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index 2ac455fda..9dc6e387d 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h index 2ee9545d4..7578aaf2a 100644 --- a/src/cryptonote_basic/account.h +++ b/src/cryptonote_basic/account.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/account_boost_serialization.h b/src/cryptonote_basic/account_boost_serialization.h index ce0e4501a..ca1a1aa29 100644 --- a/src/cryptonote_basic/account_boost_serialization.h +++ b/src/cryptonote_basic/account_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/blobdatatype.h b/src/cryptonote_basic/blobdatatype.h index 49735c034..98d05022c 100644 --- a/src/cryptonote_basic/blobdatatype.h +++ b/src/cryptonote_basic/blobdatatype.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/connection_context.cpp b/src/cryptonote_basic/connection_context.cpp index 4395bad9f..37cf31de4 100644 --- a/src/cryptonote_basic/connection_context.cpp +++ b/src/cryptonote_basic/connection_context.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index 818999a60..e88c4ba7e 100644 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 127958796..ae112c31f 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -182,7 +182,7 @@ namespace cryptonote BEGIN_SERIALIZE() VARINT_FIELD(version) - if(version == 0 || CURRENT_TRANSACTION_VERSION < version) return false; + if((version == 0 || CURRENT_TRANSACTION_VERSION < version)) return false; VARINT_FIELD(unlock_time) FIELD(vin) FIELD(vout) diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index c60a62689..9bde20609 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index b423573c5..984bee19f 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -39,15 +39,6 @@ namespace cryptonote { /************************************************************************/ /* */ /************************************************************************/ - template<class t_array> - struct array_hasher: std::unary_function<t_array&, std::size_t> - { - std::size_t operator()(const t_array& val) const - { - return boost::hash_range(&val.data[0], &val.data[sizeof(val.data)]); - } - }; - #pragma pack(push, 1) struct public_address_outer_blob diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 493c9d91d..954f429cb 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 388013f96..cdbcf48c2 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -989,7 +989,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index) + bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index, hw::device* hwdev) { // If there is no view tag to check, the output can possibly belong to the account. // Will need to derive the output pub key to be certain whether or not the output belongs to the account. @@ -1002,7 +1002,15 @@ namespace cryptonote // Therefore can fail out early to avoid expensive crypto ops needlessly deriving output public key to // determine if output belongs to the account. crypto::view_tag derived_view_tag; - crypto::derive_view_tag(derivation, output_index, derived_view_tag); + if (hwdev != nullptr) + { + bool r = hwdev->derive_view_tag(derivation, output_index, derived_view_tag); + CHECK_AND_ASSERT_MES(r, false, "Failed to derive view tag"); + } + else + { + crypto::derive_view_tag(derivation, output_index, derived_view_tag); + } return view_tag == derived_view_tag; } //--------------------------------------------------------------- @@ -1012,7 +1020,7 @@ namespace cryptonote bool r = acc.get_device().generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation); CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); crypto::public_key pk; - if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + if (out_can_be_to_acc(view_tag_opt, derivation, output_index, &acc.get_device())) { r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key"); @@ -1026,7 +1034,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(output_index < additional_tx_pub_keys.size(), false, "wrong number of additional tx pubkeys"); r = acc.get_device().generate_key_derivation(additional_tx_pub_keys[output_index], acc.m_view_secret_key, derivation); CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); - if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + if (out_can_be_to_acc(view_tag_opt, derivation, output_index, &acc.get_device())) { r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key"); @@ -1040,7 +1048,7 @@ namespace cryptonote { // try the shared tx pubkey crypto::public_key subaddress_spendkey; - if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + if (out_can_be_to_acc(view_tag_opt, derivation, output_index, &hwdev)) { CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key"); auto found = subaddresses.find(subaddress_spendkey); @@ -1052,7 +1060,7 @@ namespace cryptonote if (!additional_derivations.empty()) { CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations"); - if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index)) + if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index, &hwdev)) { CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key"); auto found = subaddresses.find(subaddress_spendkey); diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 8f5459ca7..33fec238e 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -91,7 +91,7 @@ namespace cryptonote bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id); void set_tx_out(const uint64_t amount, const crypto::public_key& output_public_key, const bool use_view_tags, const crypto::view_tag& view_tag, tx_out& out); bool check_output_types(const transaction& tx, const uint8_t hf_version); - bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index); + bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index, hw::device *hwdev = nullptr); bool is_out_to_acc(const account_keys& acc, const crypto::public_key& output_public_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index, const boost::optional<crypto::view_tag>& view_tag_opt = boost::optional<crypto::view_tag>()); struct subaddress_receive_info { diff --git a/src/cryptonote_basic/cryptonote_format_utils_basic.cpp b/src/cryptonote_basic/cryptonote_format_utils_basic.cpp index 7431a1beb..fccfeccba 100644 --- a/src/cryptonote_basic/cryptonote_format_utils_basic.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils_basic.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index 165c1936e..a9777c581 100644 --- a/src/cryptonote_basic/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/difficulty.h b/src/cryptonote_basic/difficulty.h index ee9378eb9..93964646e 100644 --- a/src/cryptonote_basic/difficulty.h +++ b/src/cryptonote_basic/difficulty.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/events.h b/src/cryptonote_basic/events.h index 18ea99800..476ff6a22 100644 --- a/src/cryptonote_basic/events.h +++ b/src/cryptonote_basic/events.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/cryptonote_basic/fwd.h b/src/cryptonote_basic/fwd.h index 54f3c0184..dd0d3aec3 100644 --- a/src/cryptonote_basic/fwd.h +++ b/src/cryptonote_basic/fwd.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index a0ea3633f..aa10e4709 100644 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/hardfork.h b/src/cryptonote_basic/hardfork.h index 32f669c71..9b2fef0e1 100644 --- a/src/cryptonote_basic/hardfork.h +++ b/src/cryptonote_basic/hardfork.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -231,6 +231,11 @@ namespace cryptonote */ uint64_t get_window_size() const { return window_size; } + /** + * @brief returns info for all known hard forks + */ + const std::vector<hardfork_t>& get_hardforks() const { return heights; } + private: uint8_t get_block_version(uint64_t height) const; diff --git a/src/cryptonote_basic/merge_mining.cpp b/src/cryptonote_basic/merge_mining.cpp index b8d16aade..0e649e095 100644 --- a/src/cryptonote_basic/merge_mining.cpp +++ b/src/cryptonote_basic/merge_mining.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/cryptonote_basic/merge_mining.h b/src/cryptonote_basic/merge_mining.h index 8fe282a76..acb70ebf0 100644 --- a/src/cryptonote_basic/merge_mining.h +++ b/src/cryptonote_basic/merge_mining.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 5b0db9518..249896ee8 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -82,6 +82,7 @@ using namespace epee; #include "miner.h" +#include "crypto/hash.h" extern "C" void slow_hash_allocate_state(); @@ -436,7 +437,6 @@ namespace cryptonote { m_stop = true; } - extern "C" void rx_stop_mining(void); //----------------------------------------------------------------------------------------------------- bool miner::stop() { @@ -469,7 +469,6 @@ namespace cryptonote MINFO("Mining has been stopped, " << m_threads.size() << " finished" ); m_threads.clear(); m_threads_autodetect.clear(); - rx_stop_mining(); return true; } //----------------------------------------------------------------------------------------------------- @@ -524,6 +523,8 @@ namespace cryptonote bool miner::worker_thread() { const uint32_t th_local_index = m_thread_index++; // atomically increment, getting value before increment + crypto::rx_set_miner_thread(th_local_index, tools::get_max_concurrency()); + MLOG_SET_THREAD_NAME(std::string("[miner ") + std::to_string(th_local_index) + "]"); MGINFO("Miner thread was started ["<< th_local_index << "]"); uint32_t nonce = m_starter_nonce + th_local_index; diff --git a/src/cryptonote_basic/miner.h b/src/cryptonote_basic/miner.h index 72dc12762..35b988bd7 100644 --- a/src/cryptonote_basic/miner.h +++ b/src/cryptonote_basic/miner.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/subaddress_index.h b/src/cryptonote_basic/subaddress_index.h index 612d1e8a5..788040ae4 100644 --- a/src/cryptonote_basic/subaddress_index.h +++ b/src/cryptonote_basic/subaddress_index.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index 141f72352..af6ee5197 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_basic/verification_context.h b/src/cryptonote_basic/verification_context.h index 34157218f..11d54ae9f 100644 --- a/src/cryptonote_basic/verification_context.h +++ b/src/cryptonote_basic/verification_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -42,7 +42,12 @@ namespace cryptonote static_assert(unsigned(relay_method::none) == 0, "default m_relay initialization is not to relay_method::none"); relay_method m_relay; // gives indication on how tx should be relayed (if at all) - bool m_verifivation_failed; //bad tx, should drop connection + bool m_verifivation_failed; //bad tx, tx should not enter mempool and connection should be dropped unless m_no_drop_offense + // Do not add to mempool, do not relay, but also do not punish the peer for sending or drop + // connections to them. Used for low fees, tx_extra too big, "relay-only rules". Not to be + // confused with breaking soft fork rules, because tx could be later added to the chain if mined + // because it does not violate consensus rules. + bool m_no_drop_offense; bool m_verifivation_impossible; //the transaction is related with an alternative blockchain bool m_added_to_pool; bool m_low_mixin; @@ -53,6 +58,7 @@ namespace cryptonote bool m_overspend; bool m_fee_too_low; bool m_too_few_outputs; + bool m_tx_extra_too_big; }; struct block_verification_context diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 2ec194ef8..45da75b66 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -206,6 +206,11 @@ #define DNS_BLOCKLIST_LIFETIME (86400 * 8) +//The limit is enough for the mandatory transaction content with 16 outputs (547 bytes), +//a custom tag (1 byte) and up to 32 bytes of custom data for each recipient. +// (1+32) + (1+1+16*32) + (1+16*32) = 1060 +#define MAX_TX_EXTRA_SIZE 1060 + // New constants are intended to go here namespace config { @@ -248,6 +253,7 @@ namespace config const unsigned char HASH_KEY_MM_SLOT = 'm'; const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS_SEED[] = "multisig_tx_privkeys_seed"; const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS[] = "multisig_tx_privkeys"; + const constexpr char HASH_KEY_TXHASH_AND_MIXRING[] = "txhash_and_mixring"; // Multisig const uint32_t MULTISIG_MAX_SIGNERS{16}; diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 69411e379..373732b3b 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # @@ -31,7 +31,9 @@ set(cryptonote_core_sources cryptonote_core.cpp tx_pool.cpp tx_sanity_check.cpp - cryptonote_tx_utils.cpp) + cryptonote_tx_utils.cpp + tx_verification_utils.cpp +) set(cryptonote_core_headers) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index c37dfe9e7..dc20bd1ff 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -48,7 +48,6 @@ #include "file_io_utils.h" #include "int-util.h" #include "common/threadpool.h" -#include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" #include "cryptonote_core.h" @@ -57,6 +56,7 @@ #include "common/notify.h" #include "common/varint.h" #include "common/pruning.h" +#include "common/data_cache.h" #include "time_helper.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -98,7 +98,8 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool) : m_difficulty_for_next_block(1), m_btc_valid(false), m_batch_success(true), - m_prepare_height(0) + m_prepare_height(0), + m_rct_ver_cache() { LOG_PRINT_L3("Blockchain::" << __func__); } @@ -456,6 +457,14 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline if (!update_next_cumulative_weight_limit()) return false; } + + if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION) + { + const crypto::hash seedhash = get_block_id_by_height(crypto::rx_seedheight(m_db->height())); + if (seedhash != crypto::null_hash) + rx_set_main_seedhash(seedhash.data, tools::get_max_concurrency()); + } + return true; } //------------------------------------------------------------------ @@ -570,6 +579,12 @@ void Blockchain::pop_blocks(uint64_t nblocks) if (stop_batch) m_db->batch_stop(); + + if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION) + { + const crypto::hash seedhash = get_block_id_by_height(crypto::rx_seedheight(m_db->height())); + rx_set_main_seedhash(seedhash.data, tools::get_max_concurrency()); + } } //------------------------------------------------------------------ // This function tells BlockchainDB to remove the top block from the @@ -1239,18 +1254,20 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info> } m_hardfork->reorganize_from_chain_height(split_height); - get_block_longhash_reorg(split_height); std::shared_ptr<tools::Notify> reorg_notify = m_reorg_notify; if (reorg_notify) reorg_notify->notify("%s", std::to_string(split_height).c_str(), "%h", std::to_string(m_db->height()).c_str(), "%n", std::to_string(m_db->height() - split_height).c_str(), "%d", std::to_string(discarded_blocks).c_str(), NULL); + const uint64_t new_height = m_db->height(); + const crypto::hash seedhash = get_block_id_by_height(crypto::rx_seedheight(new_height)); + crypto::hash prev_id; if (!get_block_hash(alt_chain.back().bl, prev_id)) MERROR("Failed to get block hash of an alternative chain's tip"); else - send_miner_notifications(prev_id, alt_chain.back().already_generated_coins); + send_miner_notifications(new_height, seedhash, prev_id, alt_chain.back().already_generated_coins); for (const auto& notifier : m_block_notifiers) { @@ -1262,6 +1279,9 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info> } } + if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION) + rx_set_main_seedhash(seedhash.data, tools::get_max_concurrency()); + MGINFO_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_db->height()); return true; } @@ -2001,7 +2021,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id { seedhash = get_block_id_by_height(seedheight); } - get_altblock_longhash(bei.bl, proof_of_work, get_current_blockchain_height(), bei.height, seedheight, seedhash); + get_altblock_longhash(bei.bl, proof_of_work, seedhash); } else { get_block_longhash(this, bei.bl, proof_of_work, bei.height, 0); @@ -2044,7 +2064,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id cryptonote::blobdata blob; if (m_tx_pool.have_tx(txid, relay_category::legacy)) { - if (m_tx_pool.get_transaction_info(txid, td)) + if (m_tx_pool.get_transaction_info(txid, td, true/*include_sensitive_data*/)) { bei.block_cumulative_weight += td.weight; } @@ -3192,7 +3212,7 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const } return false; } -bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys) const +bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys) { PERF_TIMER(expand_transaction_2); CHECK_AND_ASSERT_MES(tx.version == 2, false, "Transaction version is not 2"); @@ -3434,7 +3454,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, std::vector < uint64_t > results; results.resize(tx.vin.size(), 0); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); int threads = tpool.get_max_concurrency(); @@ -3515,6 +3535,13 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, false, "Transaction spends at least one output which is too young"); } + // Warn that new RCT types are present, and thus the cache is not being used effectively + static constexpr const std::uint8_t RCT_CACHE_TYPE = rct::RCTTypeBulletproofPlus; + if (tx.rct_signatures.type > RCT_CACHE_TYPE) + { + MWARNING("RCT cache is not caching new verification results. Please update RCT_CACHE_TYPE!"); + } + if (tx.version == 1) { if (threads > 1) @@ -3536,12 +3563,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } else { - if (!expand_transaction_2(tx, tx_prefix_hash, pubkeys)) - { - MERROR_VER("Failed to expand rct signatures!"); - return false; - } - // from version 2, check ringct signatures // obviously, the original and simple rct APIs use a mixRing that's indexes // in opposite orders, because it'd be too simple otherwise... @@ -3559,61 +3580,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, case rct::RCTTypeCLSAG: case rct::RCTTypeBulletproofPlus: { - // check all this, either reconstructed (so should really pass), or not - { - if (pubkeys.size() != rv.mixRing.size()) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); - return false; - } - for (size_t i = 0; i < pubkeys.size(); ++i) - { - if (pubkeys[i].size() != rv.mixRing[i].size()) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); - return false; - } - } - - for (size_t n = 0; n < pubkeys.size(); ++n) - { - for (size_t m = 0; m < pubkeys[n].size(); ++m) - { - if (pubkeys[n][m].dest != rct::rct2pk(rv.mixRing[n][m].dest)) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); - return false; - } - if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[n][m].mask)) - { - MERROR_VER("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); - return false; - } - } - } - } - - const size_t n_sigs = rct::is_rct_clsag(rv.type) ? rv.p.CLSAGs.size() : rv.p.MGs.size(); - if (n_sigs != tx.vin.size()) - { - MERROR_VER("Failed to check ringct signatures: mismatched MGs/vin sizes"); - return false; - } - for (size_t n = 0; n < tx.vin.size(); ++n) - { - bool error; - if (rct::is_rct_clsag(rv.type)) - error = memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32); - else - error = rv.p.MGs[n].II.empty() || memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32); - if (error) - { - MERROR_VER("Failed to check ringct signatures: mismatched key image"); - return false; - } - } - - if (!rct::verRctNonSemanticsSimple(rv)) + if (!ver_rct_non_semantics_simple_cached(tx, pubkeys, m_rct_ver_cache, RCT_CACHE_TYPE)) { MERROR_VER("Failed to check ringct signatures!"); return false; @@ -3622,6 +3589,12 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } case rct::RCTTypeFull: { + if (!expand_transaction_2(tx, tx_prefix_hash, pubkeys)) + { + MERROR_VER("Failed to expand rct signatures!"); + return false; + } + // check all this, either reconstructed (so should really pass), or not { bool size_matches = true; @@ -3868,7 +3841,7 @@ void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_block epee::misc_utils::rolling_median_t<uint64_t> rm = m_long_term_block_weights_cache_rolling_median; for (size_t i = 0; i < grace_blocks; ++i) rm.insert(0); - const uint64_t Mlw_penalty_free_zone_for_wallet = std::max<uint64_t>(rm.median(), CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5); + const uint64_t Mlw_penalty_free_zone_for_wallet = std::max<uint64_t>(rm.size() == 0 ? 0 : rm.median(), CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5); // Msw: median over [100 - grace blocks] past + [grace blocks] future blocks CHECK_AND_ASSERT_THROW_MES(grace_blocks <= 100, "Grace blocks invalid In 2021 fee scaling estimate."); @@ -4552,11 +4525,15 @@ leave: } } - send_miner_notifications(id, already_generated_coins); + const crypto::hash seedhash = get_block_id_by_height(crypto::rx_seedheight(new_height)); + send_miner_notifications(new_height, seedhash, id, already_generated_coins); for (const auto& notifier: m_block_notifiers) notifier(new_height - 1, {std::addressof(bl), 1}); + if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION) + rx_set_main_seedhash(seedhash.data, tools::get_max_concurrency()); + return true; } //------------------------------------------------------------------ @@ -5137,7 +5114,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete return true; bool blocks_exist = false; - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); unsigned threads = tpool.get_max_concurrency(); blocks.resize(blocks_entry.size()); @@ -5761,24 +5738,15 @@ void Blockchain::cache_block_template(const block &b, const cryptonote::account_ m_btc_valid = true; } -void Blockchain::send_miner_notifications(const crypto::hash &prev_id, uint64_t already_generated_coins) +void Blockchain::send_miner_notifications(uint64_t height, const crypto::hash &seed_hash, const crypto::hash &prev_id, uint64_t already_generated_coins) { if (m_miner_notifiers.empty()) return; - const uint64_t height = m_db->height(); const uint8_t major_version = m_hardfork->get_ideal_version(height); const difficulty_type diff = get_difficulty_for_next_block(); const uint64_t median_weight = m_current_block_cumul_weight_median; - crypto::hash seed_hash = crypto::null_hash; - if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION) - { - uint64_t seed_height, next_height; - crypto::rx_seedheights(height, &seed_height, &next_height); - seed_hash = get_block_id_by_height(seed_height); - } - std::vector<tx_block_template_backlog_entry> tx_backlog; m_tx_pool.get_block_template_backlog(tx_backlog); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 7a94f6358..a45d3ec60 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -57,6 +57,7 @@ #include "rpc/core_rpc_server_commands_defs.h" #include "cryptonote_basic/difficulty.h" #include "cryptonote_tx_utils.h" +#include "tx_verification_utils.h" #include "cryptonote_basic/verification_context.h" #include "crypto/hash.h" #include "checkpoints/checkpoints.h" @@ -159,6 +160,13 @@ namespace cryptonote bool deinit(); /** + * @brief get a set of blockchain checkpoint hashes + * + * @return set of blockchain checkpoint hashes + */ + const checkpoints& get_checkpoints() const { return m_checkpoints; } + + /** * @brief assign a set of blockchain checkpoint hashes * * @param chk_pts the set of checkpoints to assign @@ -590,6 +598,15 @@ namespace cryptonote bool store_blockchain(); /** + * @brief expands v2 transaction data from blockchain + * + * RingCT transactions do not transmit some of their data if it + * can be reconstituted by the receiver. This function expands + * that implicit data. + */ + static bool expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys); + + /** * @brief validates a transaction's inputs * * validates a transaction's inputs as correctly used and not previously @@ -893,6 +910,13 @@ namespace cryptonote uint64_t get_earliest_ideal_height_for_version(uint8_t version) const { return m_hardfork->get_earliest_ideal_height_for_version(version); } /** + * @brief returns info for all known hard forks + * + * @return the hardforks + */ + const std::vector<hardfork_t>& get_hardforks() const { return m_hardfork->get_hardforks(); } + + /** * @brief get information about hardfork voting for a version * * @param version the version in question @@ -1208,6 +1232,9 @@ namespace cryptonote uint64_t m_prepare_nblocks; std::vector<block> *m_prepare_blocks; + // cache for verifying transaction RCT non semantics + mutable rct_ver_cache_t m_rct_ver_cache; + /** * @brief collects the keys for all outputs being "spent" as an input * @@ -1561,15 +1588,6 @@ namespace cryptonote void load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints); /** - * @brief expands v2 transaction data from blockchain - * - * RingCT transactions do not transmit some of their data if it - * can be reconstituted by the receiver. This function expands - * that implicit data. - */ - bool expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys) const; - - /** * @brief invalidates any cached block template */ void invalidate_block_template_cache(); @@ -1584,9 +1602,11 @@ namespace cryptonote /** * @brief sends new block notifications to ZMQ `miner_data` subscribers * + * @param height current blockchain height + * @param seed_hash seed hash to use for mining * @param prev_id hash of new blockchain tip * @param already_generated_coins total coins mined by the network so far */ - void send_miner_notifications(const crypto::hash &prev_id, uint64_t already_generated_coins); + void send_miner_notifications(uint64_t height, const crypto::hash &seed_hash, const crypto::hash &prev_id, uint64_t already_generated_coins); }; } // namespace cryptonote diff --git a/src/cryptonote_core/blockchain_storage_boost_serialization.h b/src/cryptonote_core/blockchain_storage_boost_serialization.h index d166caf76..3c6f87886 100644 --- a/src/cryptonote_core/blockchain_storage_boost_serialization.h +++ b/src/cryptonote_core/blockchain_storage_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index a78f5d673..72ddd0dc9 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -252,6 +252,10 @@ namespace cryptonote m_pprotocol = &m_protocol_stub; } //----------------------------------------------------------------------------------- + const checkpoints& core::get_checkpoints() const + { + return m_blockchain_storage.get_checkpoints(); + } void core::set_checkpoints(checkpoints&& chk_pts) { m_blockchain_storage.set_checkpoints(std::move(chk_pts)); @@ -1009,7 +1013,7 @@ namespace cryptonote CRITICAL_REGION_LOCAL(m_incoming_tx_lock); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); epee::span<tx_blob_entry>::const_iterator it = tx_blobs.begin(); for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { @@ -1095,7 +1099,7 @@ namespace cryptonote else if(tvc[i].m_verifivation_impossible) {MERROR_VER("Transaction verification impossible: " << results[i].hash);} - if(tvc[i].m_added_to_pool) + if(tvc[i].m_added_to_pool && results[i].tx.extra.size() <= MAX_TX_EXTRA_SIZE) { MDEBUG("tx added: " << results[i].hash); valid_events = true; @@ -1406,21 +1410,66 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::notify_txpool_event(const epee::span<const cryptonote::blobdata> tx_blobs, epee::span<const crypto::hash> tx_hashes, epee::span<const cryptonote::transaction> txs, const std::vector<bool> &just_broadcasted) const + { + if (!m_zmq_pub) + return true; + + if (tx_blobs.size() != tx_hashes.size() || tx_blobs.size() != txs.size() || tx_blobs.size() != just_broadcasted.size()) + return false; + + /* Publish txs via ZMQ that are "just broadcasted" by the daemon. This is + done here in addition to `handle_incoming_txs` in order to guarantee txs + are pub'd via ZMQ when we know the daemon has/will broadcast to other + nodes & *after* the tx is visible in the pool. This should get called + when the user submits a tx to a daemon in the "fluff" epoch relaying txs + via a public network. */ + if (std::count(just_broadcasted.begin(), just_broadcasted.end(), true) == 0) + return true; + + std::vector<txpool_event> results{}; + results.resize(tx_blobs.size()); + for (std::size_t i = 0; i < results.size(); ++i) + { + results[i].tx = std::move(txs[i]); + results[i].hash = std::move(tx_hashes[i]); + results[i].blob_size = tx_blobs[i].size(); + results[i].weight = results[i].tx.pruned ? get_pruned_transaction_weight(results[i].tx) : get_transaction_weight(results[i].tx, results[i].blob_size); + results[i].res = just_broadcasted[i]; + } + + m_zmq_pub(std::move(results)); + + return true; + } + //----------------------------------------------------------------------------------------------- void core::on_transactions_relayed(const epee::span<const cryptonote::blobdata> tx_blobs, const relay_method tx_relay) { + // lock ensures duplicate txs aren't pub'd via zmq + CRITICAL_REGION_LOCAL(m_incoming_tx_lock); + std::vector<crypto::hash> tx_hashes{}; tx_hashes.resize(tx_blobs.size()); + std::vector<cryptonote::transaction> txs{}; + txs.resize(tx_blobs.size()); + for (std::size_t i = 0; i < tx_blobs.size(); ++i) { - cryptonote::transaction tx{}; - if (!parse_and_validate_tx_from_blob(tx_blobs[i], tx, tx_hashes[i])) + if (!parse_and_validate_tx_from_blob(tx_blobs[i], txs[i], tx_hashes[i])) { LOG_ERROR("Failed to parse relayed transaction"); return; } } - m_mempool.set_relayed(epee::to_span(tx_hashes), tx_relay); + + std::vector<bool> just_broadcasted{}; + just_broadcasted.reserve(tx_hashes.size()); + + m_mempool.set_relayed(epee::to_span(tx_hashes), tx_relay, just_broadcasted); + + if (m_zmq_pub && matches_category(tx_relay, relay_category::legacy)) + notify_txpool_event(tx_blobs, epee::to_span(tx_hashes), epee::to_span(txs), just_broadcasted); } //----------------------------------------------------------------------------------------------- bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, uint64_t &seed_height, crypto::hash &seed_hash) @@ -1607,10 +1656,6 @@ namespace cryptonote if (((size_t)-1) <= 0xffffffff && block_blob.size() >= 0x3fffffff) MWARNING("This block's size is " << block_blob.size() << ", closing on the 32 bit limit"); - // load json & DNS checkpoints every 10min/hour respectively, - // and verify them with respect to what blocks we already have - CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); - block lb; if (!b) { @@ -1682,6 +1727,11 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::get_pool_transactions_info(const std::vector<crypto::hash>& txids, std::vector<std::pair<crypto::hash, tx_memory_pool::tx_details>>& txs, bool include_sensitive_txes) const + { + return m_mempool.get_transactions_info(txids, txs, include_sensitive_txes); + } + //----------------------------------------------------------------------------------------------- bool core::get_pool_transactions(std::vector<transaction>& txs, bool include_sensitive_data) const { m_mempool.get_transactions(txs, include_sensitive_data); @@ -1694,6 +1744,11 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::get_pool_info(time_t start_time, bool include_sensitive_txes, size_t max_tx_count, std::vector<std::pair<crypto::hash, tx_memory_pool::tx_details>>& added_txs, std::vector<crypto::hash>& remaining_added_txids, std::vector<crypto::hash>& removed_txs, bool& incremental) const + { + return m_mempool.get_pool_info(start_time, include_sensitive_txes, max_tx_count, added_txs, remaining_added_txids, removed_txs, incremental); + } + //----------------------------------------------------------------------------------------------- bool core::get_pool_transaction_stats(struct txpool_stats& stats, bool include_sensitive_data) const { m_mempool.get_transaction_stats(stats, include_sensitive_data); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 0b36730b6..e0655dfa2 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -437,6 +437,13 @@ namespace cryptonote void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); /** + * @copydoc Blockchain::get_checkpoints + * + * @note see Blockchain::get_checkpoints() + */ + const checkpoints& get_checkpoints() const; + + /** * @copydoc Blockchain::set_checkpoints * * @note see Blockchain::set_checkpoints() @@ -503,6 +510,23 @@ namespace cryptonote bool get_pool_transaction_hashes(std::vector<crypto::hash>& txs, bool include_sensitive_txes = false) const; /** + * @copydoc tx_memory_pool::get_pool_transactions_info + * @param include_sensitive_txes include private transactions + * + * @note see tx_memory_pool::get_pool_transactions_info + */ + bool get_pool_transactions_info(const std::vector<crypto::hash>& txids, std::vector<std::pair<crypto::hash, tx_memory_pool::tx_details>>& txs, bool include_sensitive_txes = false) const; + + /** + * @copydoc tx_memory_pool::get_pool_info + * @param include_sensitive_txes include private transactions + * @param max_tx_count max allowed added_txs in response + * + * @note see tx_memory_pool::get_pool_info + */ + bool get_pool_info(time_t start_time, bool include_sensitive_txes, size_t max_tx_count, std::vector<std::pair<crypto::hash, tx_memory_pool::tx_details>>& added_txs, std::vector<crypto::hash>& remaining_added_txids, std::vector<crypto::hash>& removed_txs, bool& incremental) const; + + /** * @copydoc tx_memory_pool::get_transactions * @param include_sensitive_txes include private transactions * @@ -1036,6 +1060,13 @@ namespace cryptonote bool relay_txpool_transactions(); /** + * @brief sends notification of txpool events to subscribers + * + * @return true on success, false otherwise + */ + bool notify_txpool_event(const epee::span<const cryptonote::blobdata> tx_blobs, epee::span<const crypto::hash> tx_hashes, epee::span<const cryptonote::transaction> txs, const std::vector<bool> &just_broadcasted) const; + + /** * @brief checks DNS versions * * @return true on success, false otherwise diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 472026217..60b399bb5 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -437,6 +437,8 @@ namespace cryptonote if (!sort_tx_extra(tx.extra, tx.extra)) return false; + CHECK_AND_ASSERT_MES(tx.extra.size() <= MAX_TX_EXTRA_SIZE, false, "TX extra size (" << tx.extra.size() << ") is greater than max allowed (" << MAX_TX_EXTRA_SIZE << ")"); + //check money if(summary_outs_money > summary_inputs_money ) { @@ -669,10 +671,10 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - void get_altblock_longhash(const block& b, crypto::hash& res, const uint64_t main_height, const uint64_t height, const uint64_t seed_height, const crypto::hash& seed_hash) + void get_altblock_longhash(const block& b, crypto::hash& res, const crypto::hash& seed_hash) { blobdata bd = get_block_hashing_blob(b); - rx_slow_hash(main_height, seed_height, seed_hash.data, bd.data(), bd.size(), res.data, 0, 1); + rx_slow_hash(seed_hash.data, bd.data(), bd.size(), res.data); } bool get_block_longhash(const Blockchain *pbc, const blobdata& bd, crypto::hash& res, const uint64_t height, const int major_version, const crypto::hash *seed_hash, const int miners) @@ -686,20 +688,16 @@ namespace cryptonote } if (major_version >= RX_BLOCK_VERSION) { - uint64_t seed_height, main_height; crypto::hash hash; if (pbc != NULL) { - seed_height = rx_seedheight(height); + const uint64_t seed_height = rx_seedheight(height); hash = seed_hash ? *seed_hash : pbc->get_pending_block_id_by_height(seed_height); - main_height = pbc->get_current_blockchain_height(); } else { memset(&hash, 0, sizeof(hash)); // only happens when generating genesis block - seed_height = 0; - main_height = 0; } - rx_slow_hash(main_height, seed_height, hash.data, bd.data(), bd.size(), res.data, seed_hash ? 0 : miners, !!seed_hash); + rx_slow_hash(hash.data, bd.data(), bd.size(), res.data); } else { const int pow_variant = major_version >= 7 ? major_version - 6 : 0; crypto::cn_slow_hash(bd.data(), bd.size(), res, pow_variant, height); @@ -713,20 +711,10 @@ namespace cryptonote return get_block_longhash(pbc, bd, res, height, b.major_version, seed_hash, miners); } - bool get_block_longhash(const Blockchain *pbc, const block& b, crypto::hash& res, const uint64_t height, const int miners) - { - return get_block_longhash(pbc, b, res, height, NULL, miners); - } - - crypto::hash get_block_longhash(const Blockchain *pbc, const block& b, const uint64_t height, const int miners) + crypto::hash get_block_longhash(const Blockchain *pbc, const block& b, const uint64_t height, const crypto::hash *seed_hash, const int miners) { crypto::hash p = crypto::null_hash; - get_block_longhash(pbc, b, p, height, miners); + get_block_longhash(pbc, b, p, height, seed_hash, miners); return p; } - - void get_block_longhash_reorg(const uint64_t split_height) - { - rx_reorg(split_height); - } } diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 12d6b8ce5..2ee93fd8f 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -144,14 +144,10 @@ namespace cryptonote ); class Blockchain; - bool get_block_longhash(const Blockchain *pb, const blobdata& bd, crypto::hash& res, const uint64_t height, - const int major_version, const crypto::hash *seed_hash, const int miners); - bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const int miners); - bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash, const int miners); - void get_altblock_longhash(const block& b, crypto::hash& res, const uint64_t main_height, const uint64_t height, - const uint64_t seed_height, const crypto::hash& seed_hash); - crypto::hash get_block_longhash(const Blockchain *pb, const block& b, const uint64_t height, const int miners); - void get_block_longhash_reorg(const uint64_t split_height); + bool get_block_longhash(const Blockchain *pb, const blobdata& bd, crypto::hash& res, const uint64_t height, const int major_version, const crypto::hash *seed_hash, const int miners = 0); + bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash = nullptr, const int miners = 0); + crypto::hash get_block_longhash(const Blockchain *pb, const block& b, const uint64_t height, const crypto::hash *seed_hash = nullptr, const int miners = 0); + void get_altblock_longhash(const block& b, crypto::hash& res, const crypto::hash& seed_hash); } diff --git a/src/cryptonote_core/i_core_events.h b/src/cryptonote_core/i_core_events.h index 629194543..cbebd9efb 100644 --- a/src/cryptonote_core/i_core_events.h +++ b/src/cryptonote_core/i_core_events.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index a68da0e62..d86a9f5f9 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -40,7 +40,6 @@ #include "blockchain.h" #include "blockchain_db/locked_txn.h" #include "blockchain_db/blockchain_db.h" -#include "common/boost_serialization_helper.h" #include "int-util.h" #include "misc_language.h" #include "warnings.h" @@ -133,6 +132,12 @@ namespace cryptonote // class code expects unsigned values throughout if (m_next_check < time_t(0)) throw std::runtime_error{"Unexpected time_t (system clock) value"}; + + m_added_txs_start_time = (time_t)0; + m_removed_txs_start_time = (time_t)0; + // We don't set these to "now" already here as we don't know how long it takes from construction + // of the pool until it "goes to work". It's safer to set when the first actual txs enter the + // corresponding lists. } //--------------------------------------------------------------------------------- bool tx_memory_pool::add_tx(transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version) @@ -207,6 +212,7 @@ namespace cryptonote { tvc.m_verifivation_failed = true; tvc.m_fee_too_low = true; + tvc.m_no_drop_offense = true; return false; } @@ -219,6 +225,16 @@ namespace cryptonote return false; } + size_t tx_extra_size = tx.extra.size(); + if (!kept_by_block && tx_extra_size > MAX_TX_EXTRA_SIZE) + { + LOG_PRINT_L1("transaction tx-extra is too big: " << tx_extra_size << " bytes, the limit is: " << MAX_TX_EXTRA_SIZE); + tvc.m_verifivation_failed = true; + tvc.m_tx_extra_too_big = true; + tvc.m_no_drop_offense = true; + return false; + } + // if the transaction came from a block popped from the chain, // don't check if we have its key images as spent. // TODO: Investigate why not? @@ -281,7 +297,7 @@ namespace cryptonote return false; m_blockchain.add_txpool_tx(id, blob, meta); - m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); + add_tx_to_transient_lists(id, fee / (double)(tx_weight ? tx_weight : 1), receive_time); lock.commit(); } catch (const std::exception &e) @@ -352,7 +368,7 @@ namespace cryptonote m_blockchain.remove_txpool_tx(id); m_blockchain.add_txpool_tx(id, blob, meta); - m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); + add_tx_to_transient_lists(id, meta.fee / (double)(tx_weight ? tx_weight : 1), receive_time); } lock.commit(); } @@ -373,7 +389,7 @@ namespace cryptonote ++m_cookie; - MINFO("Transaction added to pool: txid " << id << " weight: " << tx_weight << " fee/byte: " << (fee / (double)(tx_weight ? tx_weight : 1))); + MINFO("Transaction added to pool: txid " << id << " weight: " << tx_weight << " fee/byte: " << (fee / (double)(tx_weight ? tx_weight : 1)) << ", count: " << m_added_txs_by_id.size()); prune(m_txpool_max_weight); @@ -402,6 +418,19 @@ namespace cryptonote m_txpool_max_weight = bytes; } //--------------------------------------------------------------------------------- + void tx_memory_pool::reduce_txpool_weight(size_t weight) + { + if (weight > m_txpool_weight) + { + MERROR("Underflow in txpool weight"); + m_txpool_weight = 0; + } + else + { + m_txpool_weight -= weight; + } + } + //--------------------------------------------------------------------------------- void tx_memory_pool::prune(size_t bytes) { CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -423,8 +452,14 @@ namespace cryptonote txpool_tx_meta_t meta; if (!m_blockchain.get_txpool_tx_meta(txid, meta)) { - MERROR("Failed to find tx_meta in txpool"); - return; + static bool warned = false; + if (!warned) + { + MERROR("Failed to find tx_meta in txpool (will only print once)"); + warned = true; + } + --it; + continue; } // don't prune the kept_by_block ones, they're likely added because we're adding a block with those if (meta.kept_by_block) @@ -442,10 +477,11 @@ namespace cryptonote // remove first, in case this throws, so key images aren't removed MINFO("Pruning tx " << txid << " from txpool: weight: " << meta.weight << ", fee/byte: " << it->first.first); m_blockchain.remove_txpool_tx(txid); - m_txpool_weight -= meta.weight; + reduce_txpool_weight(meta.weight); remove_transaction_keyimages(tx, txid); MINFO("Pruned tx " << txid << " from txpool: weight: " << meta.weight << ", fee/byte: " << it->first.first); - m_txs_by_fee_and_receive_time.erase(it--); + remove_tx_from_transient_lists(it, txid, !meta.matches(relay_category::broadcasted)); + it--; changed = true; } catch (const std::exception &e) @@ -527,8 +563,7 @@ namespace cryptonote CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - auto sorted_it = find_tx_in_sorted_container(id); - + bool sensitive = false; try { LockedTXN lock(m_blockchain.get_db()); @@ -559,10 +594,11 @@ namespace cryptonote do_not_relay = meta.do_not_relay; double_spend_seen = meta.double_spend_seen; pruned = meta.pruned; + sensitive = !meta.matches(relay_category::broadcasted); // remove first, in case this throws, so key images aren't removed m_blockchain.remove_txpool_tx(id); - m_txpool_weight -= tx_weight; + reduce_txpool_weight(tx_weight); remove_transaction_keyimages(tx, id); lock.commit(); } @@ -572,13 +608,12 @@ namespace cryptonote return false; } - if (sorted_it != m_txs_by_fee_and_receive_time.end()) - m_txs_by_fee_and_receive_time.erase(sorted_it); + remove_tx_from_transient_lists(find_tx_in_sorted_container(id), id, sensitive); ++m_cookie; return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::get_transaction_info(const crypto::hash &txid, tx_details &td) const + bool tx_memory_pool::get_transaction_info(const crypto::hash &txid, tx_details &td, bool include_sensitive_data, bool include_blob) const { PERF_TIMER(get_transaction_info); CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -590,7 +625,12 @@ namespace cryptonote txpool_tx_meta_t meta; if (!m_blockchain.get_txpool_tx_meta(txid, meta)) { - MERROR("Failed to find tx in txpool"); + LOG_PRINT_L2("Failed to find tx in txpool: " << txid); + return false; + } + if (!include_sensitive_data && !meta.matches(relay_category::broadcasted)) + { + // We don't want sensitive data && the tx is sensitive, so no need to return it return false; } cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); @@ -616,11 +656,13 @@ namespace cryptonote td.kept_by_block = meta.kept_by_block; td.last_failed_height = meta.last_failed_height; td.last_failed_id = meta.last_failed_id; - td.receive_time = meta.receive_time; - td.last_relayed_time = meta.dandelionpp_stem ? 0 : meta.last_relayed_time; + td.receive_time = include_sensitive_data ? meta.receive_time : 0; + td.last_relayed_time = (include_sensitive_data && !meta.dandelionpp_stem) ? meta.last_relayed_time : 0; td.relayed = meta.relayed; td.do_not_relay = meta.do_not_relay; td.double_spend_seen = meta.double_spend_seen; + if (include_blob) + td.tx_blob = std::move(txblob); } catch (const std::exception &e) { @@ -630,6 +672,25 @@ namespace cryptonote return true; } + //------------------------------------------------------------------ + bool tx_memory_pool::get_transactions_info(const std::vector<crypto::hash>& txids, std::vector<std::pair<crypto::hash, tx_details>>& txs, bool include_sensitive) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + txs.clear(); + + for (const auto &it: txids) + { + tx_details details; + bool success = get_transaction_info(it, details, include_sensitive, true/*include_blob*/); + if (success) + { + txs.push_back(std::make_pair(it, std::move(details))); + } + } + return true; + } //--------------------------------------------------------------------------------- bool tx_memory_pool::get_complement(const std::vector<crypto::hash> &hashes, std::vector<cryptonote::blobdata> &txes) const { @@ -691,15 +752,7 @@ namespace cryptonote (tx_age > CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME && meta.kept_by_block) ) { LOG_PRINT_L1("Tx " << txid << " removed from tx pool due to outdated, age: " << tx_age ); - auto sorted_it = find_tx_in_sorted_container(txid); - if (sorted_it == m_txs_by_fee_and_receive_time.end()) - { - LOG_PRINT_L1("Removing tx " << txid << " from tx pool, but it was not found in the sorted txs container!"); - } - else - { - m_txs_by_fee_and_receive_time.erase(sorted_it); - } + remove_tx_from_transient_lists(find_tx_in_sorted_container(txid), txid, !meta.matches(relay_category::broadcasted)); m_timed_out_transactions.insert(txid); remove.push_back(std::make_pair(txid, meta.weight)); } @@ -725,7 +778,7 @@ namespace cryptonote { // remove first, so we only remove key images if the tx removal succeeds m_blockchain.remove_txpool_tx(txid); - m_txpool_weight -= entry.second; + reduce_txpool_weight(entry.second); remove_transaction_keyimages(tx, txid); } } @@ -820,8 +873,10 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method) + void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method, std::vector<bool> &just_broadcasted) { + just_broadcasted.clear(); + crypto::random_poisson_seconds embargo_duration{dandelionpp_embargo_average}; const auto now = std::chrono::system_clock::now(); uint64_t next_relay = uint64_t{std::numeric_limits<time_t>::max()}; @@ -831,12 +886,14 @@ namespace cryptonote LockedTXN lock(m_blockchain.get_db()); for (const auto& hash : hashes) { + bool was_just_broadcasted = false; try { txpool_tx_meta_t meta; if (m_blockchain.get_txpool_tx_meta(hash, meta)) { // txes can be received as "stem" or "fluff" in either order + const bool already_broadcasted = meta.matches(relay_category::broadcasted); meta.upgrade_relay_method(method); meta.relayed = true; @@ -849,6 +906,12 @@ namespace cryptonote meta.last_relayed_time = std::chrono::system_clock::to_time_t(now); m_blockchain.update_txpool_tx(hash, meta); + // wait until db update succeeds to ensure tx is visible in the pool + was_just_broadcasted = !already_broadcasted && meta.matches(relay_category::broadcasted); + + if (was_just_broadcasted) + // Make sure the tx gets re-added with an updated time + add_tx_to_transient_lists(hash, meta.fee / (double)meta.weight, std::chrono::system_clock::to_time_t(now)); } } catch (const std::exception &e) @@ -856,6 +919,7 @@ namespace cryptonote MERROR("Failed to update txpool transaction metadata: " << e.what()); // continue } + just_broadcasted.emplace_back(was_just_broadcasted); } lock.commit(); set_if_less(m_next_check, time_t(next_relay)); @@ -900,6 +964,81 @@ namespace cryptonote }, false, category); } //------------------------------------------------------------------ + bool tx_memory_pool::get_pool_info(time_t start_time, bool include_sensitive, size_t max_tx_count, std::vector<std::pair<crypto::hash, tx_details>>& added_txs, std::vector<crypto::hash>& remaining_added_txids, std::vector<crypto::hash>& removed_txs, bool& incremental) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + incremental = true; + if (start_time == (time_t)0) + { + // Giving no start time means give back whole pool + incremental = false; + } + else if ((m_added_txs_start_time != (time_t)0) && (m_removed_txs_start_time != (time_t)0)) + { + if ((start_time <= m_added_txs_start_time) || (start_time <= m_removed_txs_start_time)) + { + // If either of the two lists do not go back far enough it's not possible to + // deliver incremental pool info + incremental = false; + } + // The check uses "<=": We cannot be sure to have ALL txs exactly at start_time, only AFTER that time + } + else + { + // Some incremental info still missing completely + incremental = false; + } + + added_txs.clear(); + remaining_added_txids.clear(); + removed_txs.clear(); + + std::vector<crypto::hash> txids; + if (!incremental) + { + LOG_PRINT_L2("Giving back the whole pool"); + // Give back the whole pool in 'added_txs'; because calling 'get_transaction_info' right inside the + // anonymous method somehow results in an LMDB error with transactions we have to build a list of + // ids first and get the full info afterwards + get_transaction_hashes(txids, include_sensitive); + if (txids.size() > max_tx_count) + { + remaining_added_txids = std::vector<crypto::hash>(txids.begin() + max_tx_count, txids.end()); + txids.erase(txids.begin() + max_tx_count, txids.end()); + } + get_transactions_info(txids, added_txs, include_sensitive); + return true; + } + + // Give back incrementally, based on time of entry into the map + for (const auto &pit : m_added_txs_by_id) + { + if (pit.second >= start_time) + txids.push_back(pit.first); + } + get_transactions_info(txids, added_txs, include_sensitive); + if (added_txs.size() > max_tx_count) + { + remaining_added_txids.reserve(added_txs.size() - max_tx_count); + for (size_t i = max_tx_count; i < added_txs.size(); ++i) + remaining_added_txids.push_back(added_txs[i].first); + added_txs.erase(added_txs.begin() + max_tx_count, added_txs.end()); + } + + std::multimap<time_t, removed_tx_info>::const_iterator rit = m_removed_txs_by_time.lower_bound(start_time); + while (rit != m_removed_txs_by_time.end()) + { + if (include_sensitive || !rit->second.sensitive) + { + removed_txs.push_back(rit->second.txid); + } + ++rit; + } + return true; + } + //------------------------------------------------------------------ void tx_memory_pool::get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive) const { CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -917,26 +1056,61 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; - backlog.reserve(m_blockchain.get_txpool_tx_count(include_sensitive)); - txpool_tx_meta_t tmp_meta; - m_blockchain.for_all_txpool_txes([this, &backlog, &tmp_meta](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){ - transaction tx; - if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx))) + + std::vector<tx_block_template_backlog_entry> tmp; + uint64_t total_weight = 0; + + // First get everything from the mempool, filter it later + m_blockchain.for_all_txpool_txes([&tmp, &total_weight](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*){ + tmp.emplace_back(tx_block_template_backlog_entry{txid, meta.weight, meta.fee}); + total_weight += meta.weight; + return true; + }, false, include_sensitive ? relay_category::all : relay_category::broadcasted); + + // Limit backlog to 112.5% of current median weight. This is enough to mine a full block with the optimal block reward + const uint64_t median_weight = m_blockchain.get_current_cumulative_block_weight_median(); + const uint64_t max_backlog_weight = median_weight + (median_weight / 8); + + // If the total weight is too high, choose the best paying transactions + if (total_weight > max_backlog_weight) + std::sort(tmp.begin(), tmp.end(), [](const auto& a, const auto& b){ return a.fee * b.weight > b.fee * a.weight; }); + + backlog.clear(); + uint64_t w = 0; + + std::unordered_set<crypto::key_image> k_images; + + for (const tx_block_template_backlog_entry& e : tmp) + { + try { - MERROR("Failed to parse tx from txpool"); - // continue - return true; - } - tx.set_hash(txid); + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(e.id, meta)) + continue; - tmp_meta = meta; + cryptonote::blobdata txblob; + if (!m_blockchain.get_txpool_tx_blob(e.id, txblob, relay_category::all)) + continue; - if (is_transaction_ready_to_go(tmp_meta, txid, *bd, tx)) - backlog.push_back({txid, meta.weight, meta.fee}); + cryptonote::transaction tx; + if (is_transaction_ready_to_go(meta, e.id, txblob, tx)) + { + if (have_key_images(k_images, tx)) + continue; + append_key_images(k_images, tx); - return true; - }, true, category); + backlog.push_back(e); + w += e.weight; + if (w > max_backlog_weight) + break; + } + } + catch (const std::exception &e) + { + MERROR("Failed to check transaction readiness: " << e.what()); + // continue, not fatal + } + } } //------------------------------------------------------------------ void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_sensitive) const @@ -1569,6 +1743,12 @@ namespace cryptonote CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); + // Simply throw away incremental info, too difficult to update + m_added_txs_by_id.clear(); + m_added_txs_start_time = (time_t)0; + m_removed_txs_by_time.clear(); + m_removed_txs_start_time = (time_t)0; + MINFO("Validating txpool contents for v" << (unsigned)version); LockedTXN lock(m_blockchain.get_db()); @@ -1626,6 +1806,106 @@ namespace cryptonote return n_removed; } //--------------------------------------------------------------------------------- + void tx_memory_pool::add_tx_to_transient_lists(const crypto::hash& txid, double fee, time_t receive_time) + { + + time_t now = time(NULL); + const std::unordered_map<crypto::hash, time_t>::iterator it = m_added_txs_by_id.find(txid); + if (it == m_added_txs_by_id.end()) + { + m_added_txs_by_id.insert(std::make_pair(txid, now)); + } + else + { + // This tx was already added to the map earlier, probably because then it was in the "stem" + // phase of Dandelion++ and now is in the "fluff" phase i.e. got broadcasted: We have to set + // a new time for clients that are not allowed to see sensitive txs to make sure they will + // see it now if they query incrementally + it->second = now; + + auto sorted_it = find_tx_in_sorted_container(txid); + if (sorted_it == m_txs_by_fee_and_receive_time.end()) + { + MERROR("Re-adding tx " << txid << " to tx pool, but it was not found in the sorted txs container"); + } + else + { + m_txs_by_fee_and_receive_time.erase(sorted_it); + } + } + m_txs_by_fee_and_receive_time.emplace(std::pair<double, time_t>(fee, receive_time), txid); + + // Don't check for "resurrected" txs in case of reorgs i.e. don't check in 'm_removed_txs_by_time' + // whether we have that txid there and if yes remove it; this results in possible duplicates + // where we return certain txids as deleted AND in the pool at the same time which requires + // clients to process deleted ones BEFORE processing pool txs + if (m_added_txs_start_time == (time_t)0) + { + m_added_txs_start_time = now; + } + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::remove_tx_from_transient_lists(const cryptonote::sorted_tx_container::iterator& sorted_it, const crypto::hash& txid, bool sensitive) + { + if (sorted_it == m_txs_by_fee_and_receive_time.end()) + { + LOG_PRINT_L1("Removing tx " << txid << " from tx pool, but it was not found in the sorted txs container!"); + } + else + { + m_txs_by_fee_and_receive_time.erase(sorted_it); + } + + const std::unordered_map<crypto::hash, time_t>::iterator it = m_added_txs_by_id.find(txid); + if (it != m_added_txs_by_id.end()) + { + m_added_txs_by_id.erase(it); + } + else + { + MDEBUG("Removing tx " << txid << " from tx pool, but it was not found in the map of added txs"); + } + track_removed_tx(txid, sensitive); + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::track_removed_tx(const crypto::hash& txid, bool sensitive) + { + time_t now = time(NULL); + m_removed_txs_by_time.insert(std::make_pair(now, removed_tx_info{txid, sensitive})); + MDEBUG("Transaction removed from pool: txid " << txid << ", total entries in removed list now " << m_removed_txs_by_time.size()); + if (m_removed_txs_start_time == (time_t)0) + { + m_removed_txs_start_time = now; + } + + // Simple system to make sure the list of removed ids does not swell to an unmanageable size: Set + // an absolute size limit plus delete entries that are x minutes old (which is ok because clients + // will sync with sensible time intervalls and should not ask for incremental info e.g. 1 hour back) + const int MAX_REMOVED = 20000; + if (m_removed_txs_by_time.size() > MAX_REMOVED) + { + auto erase_it = m_removed_txs_by_time.begin(); + std::advance(erase_it, MAX_REMOVED / 4 + 1); + m_removed_txs_by_time.erase(m_removed_txs_by_time.begin(), erase_it); + m_removed_txs_start_time = m_removed_txs_by_time.begin()->first; + MDEBUG("Erased old transactions from big removed list, leaving " << m_removed_txs_by_time.size()); + } + else + { + time_t earliest = now - (30 * 60); // 30 minutes + std::map<time_t, removed_tx_info>::iterator from, to; + from = m_removed_txs_by_time.begin(); + to = m_removed_txs_by_time.lower_bound(earliest); + int distance = std::distance(from, to); + if (distance > 0) + { + m_removed_txs_by_time.erase(from, to); + m_removed_txs_start_time = earliest; + MDEBUG("Erased " << distance << " old transactions from removed list, leaving " << m_removed_txs_by_time.size()); + } + } + } + //--------------------------------------------------------------------------------- bool tx_memory_pool::init(size_t max_txpool_weight, bool mine_stem_txes) { CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -1633,6 +1913,10 @@ namespace cryptonote m_txpool_max_weight = max_txpool_weight ? max_txpool_weight : DEFAULT_TXPOOL_MAX_WEIGHT; m_txs_by_fee_and_receive_time.clear(); + m_added_txs_by_id.clear(); + m_added_txs_start_time = (time_t)0; + m_removed_txs_by_time.clear(); + m_removed_txs_start_time = (time_t)0; m_spent_key_images.clear(); m_txpool_weight = 0; std::vector<crypto::hash> remove; @@ -1657,7 +1941,7 @@ namespace cryptonote MFATAL("Failed to insert key images from txpool tx"); return false; } - m_txs_by_fee_and_receive_time.emplace(std::pair<double, time_t>(meta.fee / (double)meta.weight, meta.receive_time), txid); + add_tx_to_transient_lists(txid, meta.fee / (double)meta.weight, meta.receive_time); m_txpool_weight += meta.weight; return true; }, true, relay_category::all); diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 62bef6c06..6fe2eea59 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -266,7 +266,11 @@ namespace cryptonote void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive = false) const; /** - * @brief get (hash, weight, fee) for all transactions in the pool - the minimum required information to create a block template + * @brief get (hash, weight, fee) for transactions in the pool - the minimum required information to create a block template + * + * Not all transactions in the pool will be returned for performance reasons + * If there are too many transactions in the pool, only the highest-paying transactions + * will be returned - but enough for the miner to create a full block * * @param backlog return-by-reference that data * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes @@ -353,8 +357,10 @@ namespace cryptonote * * @param hashes list of tx hashes that are about to be relayed * @param tx_relay update how the tx left this node + * @param just_broadcasted true if a tx was just broadcasted + * */ - void set_relayed(epee::span<const crypto::hash> hashes, relay_method tx_relay); + void set_relayed(epee::span<const crypto::hash> hashes, relay_method tx_relay, std::vector<bool> &just_broadcasted); /** * @brief get the total number of transactions in the pool @@ -406,6 +412,13 @@ namespace cryptonote */ void set_txpool_max_weight(size_t bytes); + /** + * @brief reduce the cumulative txpool weight by the weight provided + * + * @param weight the weight to reduce the total txpool weight by + */ + void reduce_txpool_weight(size_t weight); + #define CURRENT_MEMPOOL_ARCHIVE_VER 11 #define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 13 @@ -415,6 +428,7 @@ namespace cryptonote struct tx_details { transaction tx; //!< the transaction + cryptonote::blobdata tx_blob; //!< the transaction's binary blob size_t blob_size; //!< the transaction's size size_t weight; //!< the transaction's weight uint64_t fee; //!< the transaction's fee amount @@ -453,13 +467,25 @@ namespace cryptonote /** * @brief get infornation about a single transaction */ - bool get_transaction_info(const crypto::hash &txid, tx_details &td) const; + bool get_transaction_info(const crypto::hash &txid, tx_details &td, bool include_sensitive_data, bool include_blob = false) const; + + /** + * @brief get information about multiple transactions + */ + bool get_transactions_info(const std::vector<crypto::hash>& txids, std::vector<std::pair<crypto::hash, tx_details>>& txs, bool include_sensitive_data = false) const; /** * @brief get transactions not in the passed set */ bool get_complement(const std::vector<crypto::hash> &hashes, std::vector<cryptonote::blobdata> &txes) const; + /** + * @brief get info necessary for update of pool-related info in a wallet, preferably incremental + * + * @return true on success, false on error + */ + bool get_pool_info(time_t start_time, bool include_sensitive, size_t max_tx_count, std::vector<std::pair<crypto::hash, tx_details>>& added_txs, std::vector<crypto::hash>& remaining_added_txids, std::vector<crypto::hash>& removed_txs, bool& incremental) const; + private: /** @@ -564,6 +590,10 @@ namespace cryptonote */ void prune(size_t bytes = 0); + void add_tx_to_transient_lists(const crypto::hash& txid, double fee, time_t receive_time); + void remove_tx_from_transient_lists(const cryptonote::sorted_tx_container::iterator& sorted_it, const crypto::hash& txid, bool sensitive); + void track_removed_tx(const crypto::hash& txid, bool sensitive); + //TODO: confirm the below comments and investigate whether or not this // is the desired behavior //! map key images to transactions which spent them @@ -596,6 +626,26 @@ private: std::atomic<uint64_t> m_cookie; //!< incremented at each change + // Info when transactions entered the pool, accessible by txid + std::unordered_map<crypto::hash, time_t> m_added_txs_by_id; + + // Info at what time the pool started to track the adding of transactions + time_t m_added_txs_start_time; + + struct removed_tx_info + { + crypto::hash txid; + bool sensitive; + }; + + // Info about transactions that were removed from the pool, ordered by the time + // of deletion + std::multimap<time_t, removed_tx_info> m_removed_txs_by_time; + + // Info how far back in time the list of removed tx ids currently reaches + // (it gets shorted periodically to prevent overflow) + time_t m_removed_txs_start_time; + /** * @brief get an iterator to a transaction in the sorted container * diff --git a/src/cryptonote_core/tx_sanity_check.cpp b/src/cryptonote_core/tx_sanity_check.cpp index f46ec3bbc..99d5717cb 100644 --- a/src/cryptonote_core/tx_sanity_check.cpp +++ b/src/cryptonote_core/tx_sanity_check.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/tx_sanity_check.h b/src/cryptonote_core/tx_sanity_check.h index 9779a8e79..4d7bf741c 100644 --- a/src/cryptonote_core/tx_sanity_check.h +++ b/src/cryptonote_core/tx_sanity_check.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_core/tx_verification_utils.cpp b/src/cryptonote_core/tx_verification_utils.cpp new file mode 100644 index 000000000..a93ef2f25 --- /dev/null +++ b/src/cryptonote_core/tx_verification_utils.cpp @@ -0,0 +1,167 @@ +// Copyright (c) 2023, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "cryptonote_core/blockchain.h" +#include "cryptonote_core/tx_verification_utils.h" +#include "ringct/rctSigs.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "blockchain" + +#define VER_ASSERT(cond, msgexpr) CHECK_AND_ASSERT_MES(cond, false, msgexpr) + +using namespace cryptonote; + +// Do RCT expansion, then do post-expansion sanity checks, then do full non-semantics verification. +static bool expand_tx_and_ver_rct_non_sem(transaction& tx, const rct::ctkeyM& mix_ring) +{ + // Pruned transactions can not be expanded and verified because they are missing RCT data + VER_ASSERT(!tx.pruned, "Pruned transaction will not pass verRctNonSemanticsSimple"); + + // Calculate prefix hash + const crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); + + // Expand mixring, tx inputs, tx key images, prefix hash message, etc into the RCT sig + const bool exp_res = Blockchain::expand_transaction_2(tx, tx_prefix_hash, mix_ring); + VER_ASSERT(exp_res, "Failed to expand rct signatures!"); + + const rct::rctSig& rv = tx.rct_signatures; + + // Check that expanded RCT mixring == input mixring + VER_ASSERT(rv.mixRing == mix_ring, "Failed to check ringct signatures: mismatched pubkeys/mixRing"); + + // Check CLSAG/MLSAG size against transaction input + const size_t n_sigs = rct::is_rct_clsag(rv.type) ? rv.p.CLSAGs.size() : rv.p.MGs.size(); + VER_ASSERT(n_sigs == tx.vin.size(), "Failed to check ringct signatures: mismatched input sigs/vin sizes"); + + // For each input, check that the key images were copied into the expanded RCT sig correctly + for (size_t n = 0; n < n_sigs; ++n) + { + const crypto::key_image& nth_vin_image = boost::get<txin_to_key>(tx.vin[n]).k_image; + + if (rct::is_rct_clsag(rv.type)) + { + const bool ki_match = 0 == memcmp(&nth_vin_image, &rv.p.CLSAGs[n].I, 32); + VER_ASSERT(ki_match, "Failed to check ringct signatures: mismatched CLSAG key image"); + } + else + { + const bool mg_nonempty = !rv.p.MGs[n].II.empty(); + VER_ASSERT(mg_nonempty, "Failed to check ringct signatures: missing MLSAG key image"); + const bool ki_match = 0 == memcmp(&nth_vin_image, &rv.p.MGs[n].II[0], 32); + VER_ASSERT(ki_match, "Failed to check ringct signatures: mismatched MLSAG key image"); + } + } + + // Mix ring data is now known to be correctly incorporated into the RCT sig inside tx. + return rct::verRctNonSemanticsSimple(rv); +} + +// Create a unique identifier for pair of tx blob + mix ring +static crypto::hash calc_tx_mixring_hash(const transaction& tx, const rct::ctkeyM& mix_ring) +{ + std::stringstream ss; + + // Start with domain seperation + ss << config::HASH_KEY_TXHASH_AND_MIXRING; + + // Then add TX hash + const crypto::hash tx_hash = get_transaction_hash(tx); + ss.write(tx_hash.data, sizeof(crypto::hash)); + + // Then serialize mix ring + binary_archive<true> ar(ss); + ::do_serialize(ar, const_cast<rct::ctkeyM&>(mix_ring)); + + // Calculate hash of TX hash and mix ring blob + crypto::hash tx_and_mixring_hash; + get_blob_hash(ss.str(), tx_and_mixring_hash); + + return tx_and_mixring_hash; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace cryptonote +{ + +bool ver_rct_non_semantics_simple_cached +( + transaction& tx, + const rct::ctkeyM& mix_ring, + rct_ver_cache_t& cache, + const std::uint8_t rct_type_to_cache +) +{ + // Hello future Monero dev! If you got this assert, read the following carefully: + // + // For this version of RCT, the way we guaranteed that verification caches do not generate false + // positives (and thus possibly enabling double spends) is we take a hash of two things. One, + // we use get_transaction_hash() which gives us a (cryptographically secure) unique + // representation of all "knobs" controlled by the possibly malicious constructor of the + // transaction. Two, we take a hash of all *previously validated* blockchain data referenced by + // this transaction which is required to validate the ring signature. In our case, this is the + // mixring. Future versions of the protocol may differ in this regard, but if this assumptions + // holds true in the future, enable the verification hash by modifying the `untested_tx` + // condition below. + const bool untested_tx = tx.version > 2 || tx.rct_signatures.type > rct::RCTTypeBulletproofPlus; + VER_ASSERT(!untested_tx, "Unknown TX type. Make sure RCT cache works correctly with this type and then enable it in the code here."); + + // Don't cache older (or newer) rctSig types + // This cache only makes sense when it caches data from mempool first, + // so only "current fork version-enabled" RCT types need to be cached + if (tx.rct_signatures.type != rct_type_to_cache) + { + MDEBUG("RCT cache: tx " << get_transaction_hash(tx) << " skipped"); + return expand_tx_and_ver_rct_non_sem(tx, mix_ring); + } + + // Generate unique hash for tx+mix_ring pair + const crypto::hash tx_mixring_hash = calc_tx_mixring_hash(tx, mix_ring); + + // Search cache for successful verification of same TX + mix ring combination + if (cache.has(tx_mixring_hash)) + { + MDEBUG("RCT cache: tx " << get_transaction_hash(tx) << " hit"); + return true; + } + + // We had a cache miss, so now we must expand the mix ring and do full verification + MDEBUG("RCT cache: tx " << get_transaction_hash(tx) << " missed"); + if (!expand_tx_and_ver_rct_non_sem(tx, mix_ring)) + { + return false; + } + + // At this point, the TX RCT verified successfully, so add it to the cache and return true + cache.add(tx_mixring_hash); + + return true; +} + +} // namespace cryptonote diff --git a/src/cryptonote_core/tx_verification_utils.h b/src/cryptonote_core/tx_verification_utils.h new file mode 100644 index 000000000..ccd401d2a --- /dev/null +++ b/src/cryptonote_core/tx_verification_utils.h @@ -0,0 +1,78 @@ +// Copyright (c) 2023, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "common/data_cache.h" +#include "cryptonote_basic/cryptonote_basic.h" + +namespace cryptonote +{ + +// Modifying this value should not affect consensus. You can adjust it for performance needs +static constexpr const size_t RCT_VER_CACHE_SIZE = 8192; + +using rct_ver_cache_t = ::tools::data_cache<::crypto::hash, RCT_VER_CACHE_SIZE>; + +/** + * @brief Cached version of rct::verRctNonSemanticsSimple + * + * This function will not affect how the transaction is serialized and it will never modify the + * transaction prefix. + * + * The reference to tx is mutable since the transaction's ring signatures may be expanded by + * Blockchain::expand_transaction_2. However, on cache hits, the transaction will not be + * expanded. This means that the caller does not need to call expand_transaction_2 on this + * transaction before passing it; the transaction will not successfully verify with "old" RCT data + * if the transaction has been otherwise modified since the last verification. + * + * But, if cryptonote::get_transaction_hash(tx) returns a "stale" hash, this function is not + * guaranteed to work. So make sure that the cryptonote::transaction passed has not had + * modifications to it since the last time its hash was fetched without properly invalidating the + * hashes. + * + * rct_type_to_cache can be any RCT version value as long as rct::verRctNonSemanticsSimple works for + * this RCT version, but for most applications, it doesn't make sense to not make this version + * the "current" RCT version (i.e. the version that transactions in the mempool are). + * + * @param tx transaction which contains RCT signature to verify + * @param mix_ring mixring referenced by this tx. THIS DATA MUST BE PREVIOUSLY VALIDATED + * @param cache saves tx+mixring hashes used to cache calls + * @param rct_type_to_cache Only RCT sigs with version (e.g. RCTTypeBulletproofPlus) will be cached + * @return true when verRctNonSemanticsSimple() w/ expanded tx.rct_signatures would return true + * @return false when verRctNonSemanticsSimple() w/ expanded tx.rct_signatures would return false + */ +bool ver_rct_non_semantics_simple_cached +( + transaction& tx, + const rct::ctkeyM& mix_ring, + rct_ver_cache_t& cache, + std::uint8_t rct_type_to_cache +); + +} // namespace cryptonote diff --git a/src/cryptonote_protocol/CMakeLists.txt b/src/cryptonote_protocol/CMakeLists.txt index b516e17e9..c52eaa802 100644 --- a/src/cryptonote_protocol/CMakeLists.txt +++ b/src/cryptonote_protocol/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp index 4e65eafa4..ba22157b5 100644 --- a/src/cryptonote_protocol/block_queue.cpp +++ b/src/cryptonote_protocol/block_queue.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/block_queue.h b/src/cryptonote_protocol/block_queue.h index 64ff106a3..311275aa9 100644 --- a/src/cryptonote_protocol/block_queue.h +++ b/src/cryptonote_protocol/block_queue.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index 0b860f1a8..23d9144a5 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp b/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp index 92ee08054..90f3c111a 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp +++ b/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp @@ -2,7 +2,7 @@ /// @author rfree (current maintainer in monero.cc project) /// @brief This is the place to implement our handlers for protocol network actions, e.g. for ratelimit for download-requests -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 515b78c94..a29ee8287 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -2,7 +2,7 @@ /// @author rfree (current maintainer/user in monero.cc project - most of code is from CryptoNote) /// @brief This is the original cryptonote protocol network-events handler, modified by us -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index af3031263..5dbc614ca 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -2,7 +2,7 @@ /// @author rfree (current maintainer/user in monero.cc project - most of code is from CryptoNote) /// @brief This is the original cryptonote protocol network-events handler, modified by us -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -281,6 +281,11 @@ namespace cryptonote cnx.ip = cnx.host; cnx.port = std::to_string(cntxt.m_remote_address.as<epee::net_utils::ipv4_network_address>().port()); } + else if (cntxt.m_remote_address.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()) + { + cnx.ip = cnx.host; + cnx.port = std::to_string(cntxt.m_remote_address.as<epee::net_utils::ipv6_network_address>().port()); + } cnx.rpc_port = cntxt.m_rpc_port; cnx.rpc_credits_per_hash = cntxt.m_rpc_credits_per_hash; @@ -537,6 +542,10 @@ namespace cryptonote MLOG_PEER_STATE("requesting chain"); } + // load json & DNS checkpoints every 10min/hour respectively, + // and verify them with respect to what blocks we already have + CHECK_AND_ASSERT_MES(m_core.update_checkpoints(), 1, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); + return 1; } //------------------------------------------------------------------------------------------------------------------------ @@ -819,6 +828,10 @@ namespace cryptonote post_notify<NOTIFY_REQUEST_CHAIN>(r, context); MLOG_PEER_STATE("requesting chain"); } + + // load json & DNS checkpoints every 10min/hour respectively, + // and verify them with respect to what blocks we already have + CHECK_AND_ASSERT_MES(m_core.update_checkpoints(), 1, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); } } else @@ -1012,7 +1025,7 @@ namespace cryptonote for (auto& tx : arg.txs) { tx_verification_context tvc{}; - if (!m_core.handle_incoming_tx({tx, crypto::null_hash}, tvc, tx_relay, true)) + if (!m_core.handle_incoming_tx({tx, crypto::null_hash}, tvc, tx_relay, true) && !tvc.m_no_drop_offense) { LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection"); drop_connection(context, false, false); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h index 3b2ca5e37..688ff2f37 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/enums.h b/src/cryptonote_protocol/enums.h index aeb66ed5c..19cf8078c 100644 --- a/src/cryptonote_protocol/enums.h +++ b/src/cryptonote_protocol/enums.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/fwd.h b/src/cryptonote_protocol/fwd.h index f036f41cb..7182d9ef4 100644 --- a/src/cryptonote_protocol/fwd.h +++ b/src/cryptonote_protocol/fwd.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index 83f37015f..5b420ec3f 100644 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // @@ -542,6 +542,7 @@ namespace levin i_core_events* core_; std::vector<blobdata> txs_; boost::uuids::uuid source_; + relay_method tx_relay; //! \pre Called in `zone_->strand` void operator()() @@ -549,7 +550,7 @@ namespace levin if (!zone_ || !core_ || txs_.empty()) return; - if (!zone_->fluffing) + if (!zone_->fluffing || tx_relay == relay_method::local) { core_->on_transactions_relayed(epee::to_span(txs_), relay_method::stem); for (int tries = 2; 0 < tries; tries--) @@ -589,7 +590,7 @@ namespace levin change_channels(change_channels&&) = default; change_channels(const change_channels& source) - : zone_(source.zone_), map_(source.map_.clone()) + : zone_(source.zone_), map_(source.map_.clone()), fluffing_(source.fluffing_) {} //! \pre Called within `zone_->strand`. @@ -871,7 +872,7 @@ namespace levin { // this will change a local/forward tx to stem or fluff ... zone_->strand.dispatch( - dandelionpp_notify{zone_, core_, std::move(txs), source} + dandelionpp_notify{zone_, core_, std::move(txs), source, tx_relay} ); break; } diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h index 2927eea86..52d36efb0 100644 --- a/src/cryptonote_protocol/levin_notify.h +++ b/src/cryptonote_protocol/levin_notify.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 455f72472..8a8d1d9a7 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/daemon/command_line_args.h b/src/daemon/command_line_args.h index 96fddc02d..506d75490 100644 --- a/src/daemon/command_line_args.h +++ b/src/daemon/command_line_args.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 20c906141..25b15f3b1 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index cd9e6544e..7224e36f6 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -6,7 +6,7 @@ */ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index fc5f1b3d7..6baaa7015 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/command_server.h b/src/daemon/command_server.h index babb8e85a..8e3972beb 100644 --- a/src/daemon/command_server.h +++ b/src/daemon/command_server.h @@ -9,7 +9,7 @@ Passing RPC commands: */ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/core.h b/src/daemon/core.h index fde0d6bab..c3a3116b1 100644 --- a/src/daemon/core.h +++ b/src/daemon/core.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 286c02b50..97dad9357 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 529b42d20..d652d9a4b 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/executor.cpp b/src/daemon/executor.cpp index d67bd6141..14b0f86a3 100644 --- a/src/daemon/executor.cpp +++ b/src/daemon/executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/executor.h b/src/daemon/executor.h index d59ef22fb..92fc5ca46 100644 --- a/src/daemon/executor.h +++ b/src/daemon/executor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 73d9ebce1..aa8e231a2 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -83,7 +83,7 @@ uint16_t parse_public_rpc_port(const po::variables_map &vm) } uint16_t rpc_port; - if (!string_tools::get_xtype_from_string(rpc_port, rpc_port_str)) + if (!epee::string_tools::get_xtype_from_string(rpc_port, rpc_port_str)) { throw std::runtime_error("invalid RPC port " + rpc_port_str); } @@ -159,6 +159,7 @@ int main(int argc, char const * argv[]) command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_port); command_line::add_arg(core_settings, daemon_args::arg_zmq_pub); command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_disabled); + command_line::add_arg(core_settings, daemonizer::arg_non_interactive); daemonizer::init_options(hidden_options, visible_options); daemonize::t_executor::init_options(core_settings); @@ -219,6 +220,19 @@ int main(int argc, char const * argv[]) { po::store(po::parse_config_file<char>(config_path.string<std::string>().c_str(), core_settings), vm); } + catch (const po::unknown_option &e) + { + std::string unrecognized_option = e.get_option_name(); + if (all_options.find_nothrow(unrecognized_option, false)) + { + std::cerr << "Option '" << unrecognized_option << "' is not allowed in the config file, please use it as a command line flag." << std::endl; + } + else + { + std::cerr << "Unrecognized option '" << unrecognized_option << "' in config file." << std::endl; + } + return 1; + } catch (const std::exception &e) { // log system isn't initialized yet diff --git a/src/daemon/p2p.h b/src/daemon/p2p.h index 799dc3d25..40d7ab695 100644 --- a/src/daemon/p2p.h +++ b/src/daemon/p2p.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/protocol.h b/src/daemon/protocol.h index 733bacfc9..6e960168a 100644 --- a/src/daemon/protocol.h +++ b/src/daemon/protocol.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/rpc.h b/src/daemon/rpc.h index 7b059ebd3..f7bdb921e 100644 --- a/src/daemon/rpc.h +++ b/src/daemon/rpc.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index b6364ff77..fbf26db85 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -644,7 +644,14 @@ bool t_rpc_command_executor::print_connections() { } } - tools::msg_writer() << std::setw(30) << std::left << "Remote Host" + auto longest_host = *std::max_element(res.connections.begin(), res.connections.end(), + [](const auto &info1, const auto &info2) + { + return info1.address.length() < info2.address.length(); + }); + int host_field_width = std::max(15, 8 + (int) longest_host.address.length()); + + tools::msg_writer() << std::setw(host_field_width) << std::left << "Remote Host" << std::setw(8) << "Type" << std::setw(6) << "SSL" << std::setw(20) << "Peer id" @@ -661,11 +668,11 @@ bool t_rpc_command_executor::print_connections() { for (auto & info : res.connections) { std::string address = info.incoming ? "INC " : "OUT "; - address += info.ip + ":" + info.port; + address += info.address; //std::string in_out = info.incoming ? "INC " : "OUT "; tools::msg_writer() //<< std::setw(30) << std::left << in_out - << std::setw(30) << std::left << address + << std::setw(host_field_width) << std::left << address << std::setw(8) << (get_address_type_name((epee::net_utils::address_type)info.address_type)) << std::setw(6) << (info.ssl ? "yes" : "no") << std::setw(20) << info.peer_id @@ -775,7 +782,7 @@ bool t_rpc_command_executor::print_blockchain_info(int64_t start_block_index, ui return true; } } - if (start_block_index < 0 && (uint64_t)-start_block_index >= ires.height) + if ((uint64_t)-start_block_index >= ires.height) { tools::fail_msg_writer() << "start offset is larger than blockchain height"; return true; @@ -1063,7 +1070,7 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash, cryptonote::blobdata blob; std::string source = as_hex.empty() ? pruned_as_hex + prunable_as_hex : as_hex; bool pruned = !pruned_as_hex.empty() && prunable_as_hex.empty(); - if (!string_tools::parse_hexstr_to_binbuff(source, blob)) + if (!epee::string_tools::parse_hexstr_to_binbuff(source, blob)) { tools::fail_msg_writer() << "Failed to parse tx to get json format"; } diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index ebd7eda85..3d4582422 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -6,7 +6,7 @@ */ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/CMakeLists.txt b/src/daemonizer/CMakeLists.txt index 61999b3a5..5be9fda42 100644 --- a/src/daemonizer/CMakeLists.txt +++ b/src/daemonizer/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/daemonizer/daemonizer.h b/src/daemonizer/daemonizer.h index fa19c28b0..8c76c0004 100644 --- a/src/daemonizer/daemonizer.h +++ b/src/daemonizer/daemonizer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -57,6 +57,11 @@ namespace daemonizer , T_executor && executor // universal ref , boost::program_options::variables_map const & vm ); + + const command_line::arg_descriptor<bool> arg_non_interactive = { + "non-interactive" + , "Run non-interactive" + }; } #ifdef WIN32 diff --git a/src/daemonizer/posix_daemonizer.inl b/src/daemonizer/posix_daemonizer.inl index bd2741039..82b87cc97 100644 --- a/src/daemonizer/posix_daemonizer.inl +++ b/src/daemonizer/posix_daemonizer.inl @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -47,10 +47,6 @@ namespace daemonizer "pidfile" , "File path to write the daemon's PID to (optional, requires --detach)" }; - const command_line::arg_descriptor<bool> arg_non_interactive = { - "non-interactive" - , "Run non-interactive" - }; } inline void init_options( @@ -60,7 +56,6 @@ namespace daemonizer { command_line::add_arg(normal_options, arg_detach); command_line::add_arg(normal_options, arg_pidfile); - command_line::add_arg(normal_options, arg_non_interactive); } inline boost::filesystem::path get_default_data_dir() diff --git a/src/daemonizer/posix_fork.h b/src/daemonizer/posix_fork.h index b1d82ff43..497643699 100644 --- a/src/daemonizer/posix_fork.h +++ b/src/daemonizer/posix_fork.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/windows_daemonizer.inl b/src/daemonizer/windows_daemonizer.inl index a0086408f..dbe8e4a02 100644 --- a/src/daemonizer/windows_daemonizer.inl +++ b/src/daemonizer/windows_daemonizer.inl @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -61,11 +61,6 @@ namespace daemonizer "run-as-service" , "Hidden -- true if running as windows service" }; - const command_line::arg_descriptor<bool> arg_non_interactive = { - "non-interactive" - , "Run non-interactive" - }; - std::string get_argument_string(int argc, char const * argv[]) { std::string result = ""; @@ -87,7 +82,6 @@ namespace daemonizer command_line::add_arg(normal_options, arg_start_service); command_line::add_arg(normal_options, arg_stop_service); command_line::add_arg(hidden_options, arg_is_service); - command_line::add_arg(hidden_options, arg_non_interactive); } inline boost::filesystem::path get_default_data_dir() diff --git a/src/daemonizer/windows_service.cpp b/src/daemonizer/windows_service.cpp index 846f6c071..04ea62d2b 100644 --- a/src/daemonizer/windows_service.cpp +++ b/src/daemonizer/windows_service.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -196,7 +196,7 @@ bool install_service( , 0 //, GENERIC_EXECUTE | GENERIC_READ , SERVICE_WIN32_OWN_PROCESS - , SERVICE_DEMAND_START + , SERVICE_AUTO_START , SERVICE_ERROR_NORMAL , full_command.c_str() , nullptr diff --git a/src/daemonizer/windows_service.h b/src/daemonizer/windows_service.h index a96674d19..55b097bf9 100644 --- a/src/daemonizer/windows_service.h +++ b/src/daemonizer/windows_service.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/daemonizer/windows_service_runner.h b/src/daemonizer/windows_service_runner.h index a8e4d3b0e..5aa1a8c2f 100644 --- a/src/daemonizer/windows_service_runner.h +++ b/src/daemonizer/windows_service_runner.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -146,9 +146,6 @@ namespace windows { m_handler.run(); on_state_change_request_(SERVICE_CONTROL_STOP); - - // Ensure that the service is uninstalled - uninstall_service(m_name); } static void WINAPI on_state_change_request(DWORD control_code) diff --git a/src/debug_utilities/CMakeLists.txt b/src/debug_utilities/CMakeLists.txt index ecb0f0229..58cb713df 100644 --- a/src/debug_utilities/CMakeLists.txt +++ b/src/debug_utilities/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/debug_utilities/cn_deserialize.cpp b/src/debug_utilities/cn_deserialize.cpp index 41c397bd8..aa9ceec0e 100644 --- a/src/debug_utilities/cn_deserialize.cpp +++ b/src/debug_utilities/cn_deserialize.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/debug_utilities/dns_checks.cpp b/src/debug_utilities/dns_checks.cpp index caa0421e9..a59a08209 100644 --- a/src/debug_utilities/dns_checks.cpp +++ b/src/debug_utilities/dns_checks.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/debug_utilities/object_sizes.cpp b/src/debug_utilities/object_sizes.cpp index 40b651ab3..b6f9a3fbf 100644 --- a/src/debug_utilities/object_sizes.cpp +++ b/src/debug_utilities/object_sizes.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt index e4f1159b5..eef49ffee 100644 --- a/src/device/CMakeLists.txt +++ b/src/device/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/device/device.cpp b/src/device/device.cpp index e6cd358b6..3d3a14fd5 100644 --- a/src/device/device.cpp +++ b/src/device/device.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device/device.hpp b/src/device/device.hpp index eca91006f..1c29018e6 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -177,6 +177,7 @@ namespace hw { virtual bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) = 0; virtual bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) = 0; virtual bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) = 0; + virtual bool derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag) = 0; // alternative prototypes available in libringct rct::key scalarmultKey(const rct::key &P, const rct::key &a) diff --git a/src/device/device_cold.hpp b/src/device/device_cold.hpp index ba4d6d8ae..0738598b8 100644 --- a/src/device/device_cold.hpp +++ b/src/device/device_cold.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp index d70ece229..d938087fe 100644 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp index 7d3543652..7ee9f15b7 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -101,7 +101,7 @@ namespace hw { bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) override; bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) override; bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) override; - bool derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag); + bool derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag) override; /* ======================================================================= */ diff --git a/src/device/device_io.hpp b/src/device/device_io.hpp index b333caa13..20678568d 100644 --- a/src/device/device_io.hpp +++ b/src/device/device_io.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp index 3116a8713..472414d3b 100644 --- a/src/device/device_io_hid.cpp +++ b/src/device/device_io_hid.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device/device_io_hid.hpp b/src/device/device_io_hid.hpp index fd2ec8515..90d7d8553 100644 --- a/src/device/device_io_hid.hpp +++ b/src/device/device_io_hid.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index aa73e998c..27d080a1c 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -43,6 +43,10 @@ namespace hw { #ifdef WITH_DEVICE_LEDGER + namespace { + bool apdu_verbose =true; + } + #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "device.ledger" @@ -266,6 +270,7 @@ namespace hw { #define INS_DERIVE_PUBLIC_KEY 0x36 #define INS_DERIVE_SECRET_KEY 0x38 #define INS_GEN_KEY_IMAGE 0x3A + #define INS_DERIVE_VIEW_TAG 0x3B #define INS_SECRET_KEY_ADD 0x3C #define INS_SECRET_KEY_SUB 0x3E #define INS_GENERATE_KEYPAIR 0x40 @@ -525,6 +530,7 @@ namespace hw { {0x2c97, 0x0001, 0, 0xffa0}, {0x2c97, 0x0004, 0, 0xffa0}, {0x2c97, 0x0005, 0, 0xffa0}, + {0x2c97, 0x0006, 0, 0xffa0}, }; bool device_ledger::connect(void) { @@ -1308,6 +1314,54 @@ namespace hw { return true; } + bool device_ledger::derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag){ + #ifdef DEBUG_HWDEVICE + crypto::key_derivation derivation_x; + if ((this->mode == TRANSACTION_PARSE) && has_view_key) { + derivation_x = derivation; + } else { + derivation_x = hw::ledger::decrypt(derivation); + } + const std::size_t output_index_x = output_index; + crypto::view_tag view_tag_x; + log_hexbuffer("derive_view_tag: [[IN]] derivation ", derivation_x.data, 32); + log_message ("derive_view_tag: [[IN]] output_index", std::to_string(output_index_x)); + this->controle_device->derive_view_tag(derivation_x, output_index_x, view_tag_x); + log_hexbuffer("derive_view_tag: [[OUT]] view_tag ", &view_tag_x.data, 1); + #endif + + if ((this->mode == TRANSACTION_PARSE) && has_view_key) { + //If we are in TRANSACTION_PARSE, the given derivation has been retrieved uncrypted (wihtout the help + //of the device), so continue that way. + MDEBUG( "derive_view_tag : PARSE mode with known viewkey"); + crypto::derive_view_tag(derivation, output_index, view_tag); + } else { + AUTO_LOCK_CMD(); + int offset = set_command_header_noopt(INS_DERIVE_VIEW_TAG); + //derivation + this->send_secret((unsigned char*)derivation.data, offset); + //index + this->buffer_send[offset+0] = output_index>>24; + this->buffer_send[offset+1] = output_index>>16; + this->buffer_send[offset+2] = output_index>>8; + this->buffer_send[offset+3] = output_index>>0; + offset += 4; + + this->buffer_send[4] = offset-5; + this->length_send = offset; + this->exchange(); + + //view tag + memmove(&view_tag.data, &this->buffer_recv[0], 1); + } + + #ifdef DEBUG_HWDEVICE + hw::ledger::check1("derive_view_tag", "view_tag", &view_tag_x.data, &view_tag.data); + #endif + + return true; + } + /* ======================================================================= */ /* TRANSACTION */ /* ======================================================================= */ @@ -1548,7 +1602,6 @@ namespace hw { const size_t output_index_x = output_index; const bool need_additional_txkeys_x = need_additional_txkeys; const bool use_view_tags_x = use_view_tags; - const crypto::view_tag view_tag_x = view_tag; std::vector<crypto::secret_key> additional_tx_keys_x; for (const auto &k: additional_tx_keys) { @@ -1558,6 +1611,7 @@ namespace hw { std::vector<crypto::public_key> additional_tx_public_keys_x; std::vector<rct::key> amount_keys_x; crypto::public_key out_eph_public_key_x; + crypto::view_tag view_tag_x; log_message ("generate_output_ephemeral_keys: [[IN]] tx_version", std::to_string(tx_version_x)); //log_hexbuffer("generate_output_ephemeral_keys: [[IN]] sender_account_keys.view", sender_account_keys.m_sview_secret_key.data, 32); @@ -1575,11 +1629,15 @@ namespace hw { if(need_additional_txkeys_x) { log_hexbuffer("generate_output_ephemeral_keys: [[IN]] additional_tx_keys[oi]", additional_tx_keys_x[output_index].data, 32); } + log_message ("generate_output_ephemeral_keys: [[IN]] use_view_tags", std::to_string(use_view_tags_x)); this->controle_device->generate_output_ephemeral_keys(tx_version_x, sender_account_keys_x, txkey_pub_x, tx_key_x, dst_entr_x, change_addr_x, output_index_x, need_additional_txkeys_x, additional_tx_keys_x, additional_tx_public_keys_x, amount_keys_x, out_eph_public_key_x, use_view_tags_x, view_tag_x); if(need_additional_txkeys_x) { log_hexbuffer("additional_tx_public_keys_x: [[OUT]] additional_tx_public_keys_x", additional_tx_public_keys_x.back().data, 32); } + if(use_view_tags_x) { + log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] view_tag", &view_tag_x.data, 1); + } log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] amount_keys ", (char*)amount_keys_x.back().bytes, 32); log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] out_eph_public_key ", out_eph_public_key_x.data, 32); #endif @@ -1633,6 +1691,9 @@ namespace hw { memset(&this->buffer_send[offset], 0, 32); offset += 32; } + //use_view_tags + this->buffer_send[offset] = use_view_tags; + offset++; this->buffer_send[4] = offset-5; this->length_send = offset; @@ -1663,6 +1724,14 @@ namespace hw { recv_len -= 32; } + if (use_view_tags) + { + ASSERT_X(recv_len>=1, "Not enough data from device"); + memmove(&view_tag.data, &this->buffer_recv[offset], 1); + offset++; + recv_len -= 1; + } + // add ABPkeys this->add_output_key_mapping(dst_entr.addr.m_view_public_key, dst_entr.addr.m_spend_public_key, dst_entr.is_subaddress, is_change, need_additional_txkeys, output_index, @@ -1675,6 +1744,9 @@ namespace hw { hw::ledger::check32("generate_output_ephemeral_keys", "additional_tx_key", additional_tx_public_keys_x.back().data, additional_tx_public_keys.back().data); } hw::ledger::check32("generate_output_ephemeral_keys", "out_eph_public_key", out_eph_public_key_x.data, out_eph_public_key.data); + if (use_view_tags) { + hw::ledger::check1("generate_output_ephemeral_keys", "view_tag", &view_tag_x.data, &view_tag.data); + } #endif return true; @@ -1860,7 +1932,7 @@ namespace hw { // ====== Aout, Bout, AKout, C, v, k ====== kv_offset = data_offset; - if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) { + if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus) { C_offset = kv_offset+ (8)*outputs_size; } else { C_offset = kv_offset+ (32+32)*outputs_size; @@ -1877,7 +1949,7 @@ namespace hw { offset = set_command_header(INS_VALIDATE, 0x02, i+1); //options this->buffer_send[offset] = (i==outputs_size-1)? 0x00:0x80 ; - this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG)?0x02:0x00; + this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus)?0x02:0x00; offset += 1; //is_subaddress this->buffer_send[offset] = outKeys.is_subaddress; @@ -1898,7 +1970,7 @@ namespace hw { memmove(this->buffer_send+offset, data+C_offset,32); offset += 32; C_offset += 32; - if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) { + if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus) { //k memset(this->buffer_send+offset, 0, 32); offset += 32; diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index 074bfaa8d..071274160 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -44,7 +44,7 @@ namespace hw { /* Minimal supported version */ #define MINIMAL_APP_VERSION_MAJOR 1 - #define MINIMAL_APP_VERSION_MINOR 6 + #define MINIMAL_APP_VERSION_MINOR 8 #define MINIMAL_APP_VERSION_MICRO 0 #define VERSION(M,m,u) ((M)<<16|(m)<<8|(u)) @@ -87,10 +87,6 @@ namespace hw { #define SW_PROTOCOL_NOT_SUPPORTED 0x6e00 #define SW_UNKNOWN 0x6f00 - namespace { - bool apdu_verbose =true; - } - void set_apdu_verbose(bool verbose); class ABPkeys { @@ -249,6 +245,7 @@ namespace hw { bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) override; bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) override; bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) override; + bool derive_view_tag(const crypto::key_derivation &derivation, const size_t output_index, crypto::view_tag &view_tag) override; /* ======================================================================= */ /* TRANSACTION */ diff --git a/src/device/log.cpp b/src/device/log.cpp index 9b882b784..1a243c831 100644 --- a/src/device/log.cpp +++ b/src/device/log.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -165,6 +165,10 @@ namespace hw { void check8(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted) { check(msg, info, h, d, 8, crypted); } + + void check1(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted) { + check(msg, info, h, d, 1, crypted); + } #endif } diff --git a/src/device/log.hpp b/src/device/log.hpp index 660adc63e..92d174da1 100644 --- a/src/device/log.hpp +++ b/src/device/log.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -75,6 +75,7 @@ namespace hw { void check32(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted=false); void check8(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted=false); + void check1(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted=false); void set_check_verbose(bool verbose); #endif diff --git a/src/device_trezor/CMakeLists.txt b/src/device_trezor/CMakeLists.txt index 6688a317b..c30fb2b16 100644 --- a/src/device_trezor/CMakeLists.txt +++ b/src/device_trezor/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index 6f7ae9a6b..9c8148ed6 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -324,8 +324,8 @@ namespace trezor { std::vector<protocol::ki::MoneroTransferDetails> mtds; std::vector<protocol::ki::MoneroExportedKeyImage> kis; - protocol::ki::key_image_data(wallet, transfers, mtds, client_version() <= 1); - protocol::ki::generate_commitment(mtds, transfers, req, client_version() <= 1); + protocol::ki::key_image_data(wallet, transfers, mtds); + protocol::ki::generate_commitment(mtds, transfers, req); EVENT_PROGRESS(0.); this->set_msg_addr<messages::monero::MoneroKeyImageExportInitRequest>(req.get()); @@ -511,7 +511,7 @@ namespace trezor { tools::wallet2::signed_tx_set & signed_tx, hw::tx_aux_data & aux_data) { - CHECK_AND_ASSERT_THROW_MES(unsigned_tx.transfers.first == 0, "Unsuported non zero offset"); + CHECK_AND_ASSERT_THROW_MES(std::get<0>(unsigned_tx.transfers) == 0, "Unsuported non zero offset"); TREZOR_AUTO_LOCK_CMD(); require_connected(); @@ -522,7 +522,7 @@ namespace trezor { const size_t num_tx = unsigned_tx.txes.size(); m_num_transations_to_sign = num_tx; signed_tx.key_images.clear(); - signed_tx.key_images.resize(unsigned_tx.transfers.second.size()); + signed_tx.key_images.resize(std::get<2>(unsigned_tx.transfers).size()); for(size_t tx_idx = 0; tx_idx < num_tx; ++tx_idx) { std::shared_ptr<protocol::tx::Signer> signer; @@ -566,8 +566,8 @@ namespace trezor { cpend.key_images = key_images; // KI sync - for(size_t cidx=0, trans_max=unsigned_tx.transfers.second.size(); cidx < trans_max; ++cidx){ - signed_tx.key_images[cidx] = unsigned_tx.transfers.second[cidx].m_key_image; + for(size_t cidx=0, trans_max=std::get<2>(unsigned_tx.transfers).size(); cidx < trans_max; ++cidx){ + signed_tx.key_images[cidx] = std::get<2>(unsigned_tx.transfers)[cidx].m_key_image; } size_t num_sources = cdata.tx_data.sources.size(); @@ -579,9 +579,9 @@ namespace trezor { CHECK_AND_ASSERT_THROW_MES(src_idx < cdata.tx.vin.size(), "Invalid idx_mapped"); size_t idx_map_src = cdata.tx_data.selected_transfers[idx_mapped]; - CHECK_AND_ASSERT_THROW_MES(idx_map_src >= unsigned_tx.transfers.first, "Invalid offset"); + CHECK_AND_ASSERT_THROW_MES(idx_map_src >= std::get<0>(unsigned_tx.transfers), "Invalid offset"); - idx_map_src -= unsigned_tx.transfers.first; + idx_map_src -= std::get<0>(unsigned_tx.transfers); CHECK_AND_ASSERT_THROW_MES(idx_map_src < signed_tx.key_images.size(), "Invalid key image index"); const auto vini = boost::get<cryptonote::txin_to_key>(cdata.tx.vin[src_idx]); @@ -635,11 +635,7 @@ namespace trezor { } // Step: sort - auto perm_req = signer->step_permutation(); - if (perm_req){ - auto perm_ack = this->client_exchange<messages::monero::MoneroTransactionInputsPermutationAck>(perm_req); - signer->step_permutation_ack(perm_ack); - } + signer->sort_ki(); EVENT_PROGRESS(3, 1, 1); // Step: input_vini @@ -697,13 +693,13 @@ namespace trezor { unsigned device_trezor::client_version() { auto trezor_version = get_version(); - if (trezor_version <= pack_version(2, 0, 10)){ - throw exc::TrezorException("Trezor firmware 2.0.10 and lower are not supported. Please update."); + if (trezor_version < pack_version(2, 4, 3)){ + throw exc::TrezorException("Minimal Trezor firmware version is 2.4.3. Please update."); } - unsigned client_version = 1; - if (trezor_version >= pack_version(2, 3, 1)){ - client_version = 3; + unsigned client_version = 3; + if (trezor_version >= pack_version(2, 5, 2)){ + client_version = 4; } #ifdef WITH_TREZOR_DEBUGGING @@ -739,14 +735,6 @@ namespace trezor { CHECK_AND_ASSERT_THROW_MES(init_msg, "TransactionInitRequest is empty"); CHECK_AND_ASSERT_THROW_MES(init_msg->has_tsx_data(), "TransactionInitRequest has no transaction data"); CHECK_AND_ASSERT_THROW_MES(m_features, "Device state not initialized"); // make sure the caller did not reset features - const bool nonce_required = init_msg->tsx_data().has_payment_id() && init_msg->tsx_data().payment_id().size() > 0; - - if (nonce_required && init_msg->tsx_data().payment_id().size() == 8){ - // Versions 2.0.9 and lower do not support payment ID - if (get_version() <= pack_version(2, 0, 9)) { - throw exc::TrezorException("Trezor firmware 2.0.9 and lower does not support transactions with short payment IDs or integrated addresses. Please update."); - } - } } void device_trezor::transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data) diff --git a/src/device_trezor/device_trezor.hpp b/src/device_trezor/device_trezor.hpp index 38aeaf6b5..35bb78927 100644 --- a/src/device_trezor/device_trezor.hpp +++ b/src/device_trezor/device_trezor.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index 56f3784a7..a8a3d9f67 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 5b6920313..3ec21e157 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor.hpp b/src/device_trezor/trezor.hpp index f2a352f58..d425b3209 100644 --- a/src/device_trezor/trezor.hpp +++ b/src/device_trezor/trezor.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/debug_link.cpp b/src/device_trezor/trezor/debug_link.cpp index 1eed0a53e..ff7cf0ad6 100644 --- a/src/device_trezor/trezor/debug_link.cpp +++ b/src/device_trezor/trezor/debug_link.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/debug_link.hpp b/src/device_trezor/trezor/debug_link.hpp index b7a252833..4249222dc 100644 --- a/src/device_trezor/trezor/debug_link.hpp +++ b/src/device_trezor/trezor/debug_link.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/exceptions.hpp b/src/device_trezor/trezor/exceptions.hpp index 818b2cb6c..b66386c47 100644 --- a/src/device_trezor/trezor/exceptions.hpp +++ b/src/device_trezor/trezor/exceptions.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/messages_map.cpp b/src/device_trezor/trezor/messages_map.cpp index 313fd6820..527fe38ba 100644 --- a/src/device_trezor/trezor/messages_map.cpp +++ b/src/device_trezor/trezor/messages_map.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/messages_map.hpp b/src/device_trezor/trezor/messages_map.hpp index b1ea65e6e..f6820524e 100644 --- a/src/device_trezor/trezor/messages_map.hpp +++ b/src/device_trezor/trezor/messages_map.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp index a400e82c7..49d3af0f5 100644 --- a/src/device_trezor/trezor/protocol.cpp +++ b/src/device_trezor/trezor/protocol.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -38,6 +38,7 @@ #include <crypto/hmac-keccak.h> #include <ringct/rctSigs.h> #include <ringct/bulletproofs.h> +#include <ringct/bulletproofs_plus.h> #include "cryptonote_config.h" #include <sodium.h> #include <sodium/crypto_verify_32.h> @@ -145,8 +146,7 @@ namespace ki { bool key_image_data(wallet_shim * wallet, const std::vector<tools::wallet2::transfer_details> & transfers, - std::vector<MoneroTransferDetails> & res, - bool need_all_additionals) + std::vector<MoneroTransferDetails> & res) { for(auto & td : transfers){ ::crypto::public_key tx_pub_key = wallet->get_tx_pub_key_from_received_outs(td); @@ -159,11 +159,7 @@ namespace ki { cres.set_internal_output_index(td.m_internal_output_index); cres.set_sub_addr_major(td.m_subaddr_index.major); cres.set_sub_addr_minor(td.m_subaddr_index.minor); - if (need_all_additionals) { - for (auto &aux : additional_tx_pub_keys) { - cres.add_additional_tx_pub_keys(key_to_string(aux)); - } - } else if (!additional_tx_pub_keys.empty() && additional_tx_pub_keys.size() > td.m_internal_output_index) { + if (!additional_tx_pub_keys.empty() && additional_tx_pub_keys.size() > td.m_internal_output_index) { cres.add_additional_tx_pub_keys(key_to_string(additional_tx_pub_keys[td.m_internal_output_index])); } } @@ -194,8 +190,7 @@ namespace ki { void generate_commitment(std::vector<MoneroTransferDetails> & mtds, const std::vector<tools::wallet2::transfer_details> & transfers, - std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req, - bool need_subaddr_indices) + std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req) { req = std::make_shared<messages::monero::MoneroKeyImageExportInitRequest>(); @@ -219,16 +214,6 @@ namespace ki { auto & st = search.first->second; st.insert(cur.m_subaddr_index.minor); } - - if (need_subaddr_indices) { - for (auto &x: sub_indices) { - auto subs = req->add_subs(); - subs->set_account(x.first); - for (auto minor : x.second) { - subs->add_minor_indices(minor); - } - } - } } void live_refresh_ack(const ::crypto::secret_key & view_key_priv, @@ -399,7 +384,7 @@ namespace tx { m_tx_idx = tx_idx; m_ct.tx_data = cur_src_tx(); m_multisig = false; - m_client_version = 1; + m_client_version = 3; } void Signer::extract_payment_id(){ @@ -474,25 +459,19 @@ namespace tx { auto & cur = src.outputs[i]; auto out = dst->add_outputs(); - if (i == src.real_output || need_ring_indices || client_version() <= 1) { + if (i == src.real_output || need_ring_indices) { out->set_idx(cur.first); } - if (i == src.real_output || need_ring_keys || client_version() <= 1) { + if (i == src.real_output || need_ring_keys) { translate_rct_key(out->mutable_key(), &(cur.second)); } } dst->set_real_out_tx_key(key_to_string(src.real_out_tx_key)); dst->set_real_output_in_tx_index(src.real_output_in_tx_index); - - if (client_version() <= 1) { - for (auto &cur : src.real_out_additional_tx_keys) { - dst->add_real_out_additional_tx_keys(key_to_string(cur)); - } - } else if (!src.real_out_additional_tx_keys.empty()) { + if (!src.real_out_additional_tx_keys.empty()) { dst->add_real_out_additional_tx_keys(key_to_string(src.real_out_additional_tx_keys.at(src.real_output_in_tx_index))); } - dst->set_amount(src.amount); dst->set_rct(src.rct); dst->set_mask(key_to_string(src.mask)); @@ -532,7 +511,7 @@ namespace tx { m_ct.tx.version = 2; m_ct.tx.unlock_time = tx.unlock_time; - m_client_version = (m_aux_data->client_version ? m_aux_data->client_version.get() : 1); + m_client_version = (m_aux_data->client_version ? m_aux_data->client_version.get() : 3); tsx_data.set_version(1); tsx_data.set_client_version(client_version()); @@ -543,18 +522,13 @@ namespace tx { tsx_data.set_monero_version(std::string(MONERO_VERSION) + "|" + MONERO_VERSION_TAG); tsx_data.set_hard_fork(m_aux_data->hard_fork ? m_aux_data->hard_fork.get() : 0); - if (client_version() <= 1){ - assign_to_repeatable(tsx_data.mutable_minor_indices(), tx.subaddr_indices.begin(), tx.subaddr_indices.end()); - } - // Rsig decision auto rsig_data = tsx_data.mutable_rsig_data(); m_ct.rsig_type = get_rsig_type(tx.rct_config, tx.splitted_dsts.size()); rsig_data->set_rsig_type(m_ct.rsig_type); - if (tx.rct_config.range_proof_type != rct::RangeProofBorromean){ - m_ct.bp_version = (m_aux_data->bp_version ? m_aux_data->bp_version.get() : 1); - rsig_data->set_bp_version((uint32_t) m_ct.bp_version); - } + CHECK_AND_ASSERT_THROW_MES(tx.rct_config.range_proof_type != rct::RangeProofBorromean, "Borromean rsig not supported"); + m_ct.bp_version = (m_aux_data->bp_version ? m_aux_data->bp_version.get() : 1); + rsig_data->set_bp_version((uint32_t) m_ct.bp_version); generate_rsig_batch_sizes(m_ct.grouping_vct, m_ct.rsig_type, tx.splitted_dsts.size()); assign_to_repeatable(rsig_data->mutable_grouping(), m_ct.grouping_vct.begin(), m_ct.grouping_vct.end()); @@ -652,22 +626,6 @@ namespace tx { }); } - std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> Signer::step_permutation(){ - sort_ki(); - if (client_version() >= 2){ - return nullptr; - } - - auto res = std::make_shared<messages::monero::MoneroTransactionInputsPermutationRequest>(); - assign_to_repeatable(res->mutable_perm(), m_ct.source_permutation.begin(), m_ct.source_permutation.end()); - - return res; - } - - void Signer::step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack){ - - } - std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> Signer::step_set_vini_input(size_t idx){ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index"); CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index"); @@ -711,8 +669,10 @@ namespace tx { } void Signer::step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack){ + CHECK_AND_ASSERT_THROW_MES(is_req_bulletproof(), "Borromean rsig not supported"); cryptonote::tx_out tx_out; rct::Bulletproof bproof{}; + rct::BulletproofPlus bproof_plus{}; rct::ctkey out_pk{}; rct::ecdhTuple ecdh{}; @@ -727,7 +687,7 @@ namespace tx { rsig_buff = rsig_data.rsig(); } - if (client_version() >= 1 && rsig_data.has_mask()){ + if (rsig_data.has_mask()){ rct::key cmask{}; string_to_key(cmask, rsig_data.mask()); m_ct.rsig_gamma.emplace_back(cmask); @@ -751,22 +711,32 @@ namespace tx { memcpy(ecdh.amount.bytes, ack->ecdh_info().data(), 8); } - if (has_rsig && is_req_bulletproof() && !cn_deserialize(rsig_buff, bproof)){ - throw exc::ProtocolException("Cannot deserialize bulletproof rangesig"); - } - m_ct.tx.vout.emplace_back(tx_out); m_ct.tx_out_hmacs.push_back(ack->vouti_hmac()); m_ct.tx_out_pk.emplace_back(out_pk); m_ct.tx_out_ecdh.emplace_back(ecdh); - // ClientV0, if no rsig was generated on Trezor, do not continue. - // ClientV1+ generates BP after all masks in the current batch are generated - if (!has_rsig || (client_version() >= 1 && is_offloading())){ + rsig_v bp_obj{}; + if (has_rsig) { + bool deserialize_success; + if (is_req_bulletproof_plus()) { + deserialize_success = cn_deserialize(rsig_buff, bproof_plus); + bp_obj = bproof_plus; + } else { + deserialize_success = cn_deserialize(rsig_buff, bproof); + bp_obj = bproof; + } + if (!deserialize_success) { + throw exc::ProtocolException("Cannot deserialize bulletproof rangesig"); + } + } + + // Generates BP after all masks in the current batch are generated + if (!has_rsig || is_offloading()){ return; } - process_bproof(bproof); + process_bproof(bp_obj); m_ct.cur_batch_idx += 1; m_ct.cur_output_in_batch_idx = 0; } @@ -791,13 +761,21 @@ namespace tx { masks.push_back(m_ct.rsig_gamma[bidx]); } - auto bp = bulletproof_PROVE(amounts, masks); - auto serRsig = cn_serialize(bp); - m_ct.tx_out_rsigs.emplace_back(bp); + std::string serRsig; + if (is_req_bulletproof_plus()) { + auto bp = bulletproof_plus_PROVE(amounts, masks); + serRsig = cn_serialize(bp); + m_ct.tx_out_rsigs.emplace_back(bp); + } else { + auto bp = bulletproof_PROVE(amounts, masks); + serRsig = cn_serialize(bp); + m_ct.tx_out_rsigs.emplace_back(bp); + } + rsig_data.set_rsig(serRsig); } - void Signer::process_bproof(rct::Bulletproof & bproof){ + void Signer::process_bproof(rsig_v & bproof){ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index"); auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx]; for (size_t i = 0; i < batch_size; ++i){ @@ -806,12 +784,22 @@ namespace tx { rct::key commitment = m_ct.tx_out_pk[bidx].mask; commitment = rct::scalarmultKey(commitment, rct::INV_EIGHT); - bproof.V.push_back(commitment); + if (is_req_bulletproof_plus()) { + boost::get<rct::BulletproofPlus>(bproof).V.push_back(commitment); + } else { + boost::get<rct::Bulletproof>(bproof).V.push_back(commitment); + } } m_ct.tx_out_rsigs.emplace_back(bproof); - if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) { - throw exc::ProtocolException("Returned range signature is invalid"); + if (is_req_bulletproof_plus()) { + if (!rct::bulletproof_plus_VERIFY(boost::get<rct::BulletproofPlus>(m_ct.tx_out_rsigs.back()))) { + throw exc::ProtocolException("Returned range signature is invalid"); + } + } else { + if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) { + throw exc::ProtocolException("Returned range signature is invalid"); + } } } @@ -840,6 +828,7 @@ namespace tx { } void Signer::step_all_outs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllOutSetAck> ack, hw::device &hwdev){ + CHECK_AND_ASSERT_THROW_MES(is_req_bulletproof(), "Borromean rsig not supported"); m_ct.rv = std::make_shared<rct::rctSig>(); m_ct.rv->txnFee = ack->rv().txn_fee(); m_ct.rv->type = static_cast<uint8_t>(ack->rv().rv_type()); @@ -864,24 +853,15 @@ namespace tx { // RctSig auto num_sources = m_ct.tx_data.sources.size(); - if (is_simple() || is_req_bulletproof()){ - auto dst = &m_ct.rv->pseudoOuts; - if (is_bulletproof()){ - dst = &m_ct.rv->p.pseudoOuts; - } - - dst->clear(); - for (const auto &pseudo_out : m_ct.pseudo_outs) { - dst->emplace_back(); - string_to_key(dst->back(), pseudo_out); - } - - m_ct.rv->mixRing.resize(num_sources); - } else { - m_ct.rv->mixRing.resize(m_ct.tsx_data.mixin()); - m_ct.rv->mixRing[0].resize(num_sources); + auto dst = &m_ct.rv->p.pseudoOuts; + dst->clear(); + for (const auto &pseudo_out : m_ct.pseudo_outs) { + dst->emplace_back(); + string_to_key(dst->back(), pseudo_out); } + m_ct.rv->mixRing.resize(num_sources); + CHECK_AND_ASSERT_THROW_MES(m_ct.tx_out_pk.size() == m_ct.tx_out_ecdh.size(), "Invalid vector sizes"); for(size_t i = 0; i < m_ct.tx_out_ecdh.size(); ++i){ m_ct.rv->outPk.push_back(m_ct.tx_out_pk[i]); @@ -889,10 +869,10 @@ namespace tx { } for(size_t i = 0; i < m_ct.tx_out_rsigs.size(); ++i){ - if (is_bulletproof()){ - m_ct.rv->p.bulletproofs.push_back(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs[i])); + if (is_req_bulletproof_plus()) { + m_ct.rv->p.bulletproofs_plus.push_back(boost::get<rct::BulletproofPlus>(m_ct.tx_out_rsigs[i])); } else { - m_ct.rv->p.rangeSigs.push_back(boost::get<rct::rangeSig>(m_ct.tx_out_rsigs[i])); + m_ct.rv->p.bulletproofs.push_back(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs[i])); } } @@ -936,8 +916,8 @@ namespace tx { void Signer::step_sign_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSignInputAck> ack){ m_ct.signatures.push_back(ack->signature()); - // Sync updated pseudo_outputs, client_version>=1, HF10+ - if (client_version() >= 1 && ack->has_pseudo_out()){ + // Sync updated pseudo_outputs + if (ack->has_pseudo_out()){ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_input_idx < m_ct.pseudo_outs.size(), "Invalid pseudo-out index"); m_ct.pseudo_outs[m_ct.cur_input_idx] = ack->pseudo_out(); if (is_bulletproof()){ @@ -955,6 +935,8 @@ namespace tx { } void Signer::step_final_ack(std::shared_ptr<const messages::monero::MoneroTransactionFinalAck> ack){ + CHECK_AND_ASSERT_THROW_MES(is_clsag(), "Only CLSAGs signatures are supported"); + if (m_multisig){ auto & cout_key = ack->cout_key(); for(auto & cur : m_ct.couts){ @@ -975,47 +957,34 @@ namespace tx { m_ct.enc_keys = ack->tx_enc_keys(); // Opening the sealed signatures - if (client_version() >= 3){ - if(!ack->has_opening_key()){ - throw exc::ProtocolException("Client version 3+ requires sealed signatures"); - } + if(!ack->has_opening_key()){ + throw exc::ProtocolException("Client version 3+ requires sealed signatures"); + } - for(size_t i = 0; i < m_ct.signatures.size(); ++i){ - CHECK_AND_ASSERT_THROW_MES(m_ct.signatures[i].size() > crypto::chacha::TAG_SIZE, "Invalid signature size"); - std::string nonce = compute_sealing_key(ack->opening_key(), i, true); - std::string key = compute_sealing_key(ack->opening_key(), i, false); - size_t plen = m_ct.signatures[i].size() - crypto::chacha::TAG_SIZE; - std::unique_ptr<uint8_t[]> plaintext(new uint8_t[plen]); - uint8_t * buff = plaintext.get(); - - protocol::crypto::chacha::decrypt( - m_ct.signatures[i].data(), - m_ct.signatures[i].size(), - reinterpret_cast<const uint8_t *>(key.data()), - reinterpret_cast<const uint8_t *>(nonce.data()), - reinterpret_cast<char *>(buff), &plen); - m_ct.signatures[i].assign(reinterpret_cast<const char *>(buff), plen); - } + for(size_t i = 0; i < m_ct.signatures.size(); ++i){ + CHECK_AND_ASSERT_THROW_MES(m_ct.signatures[i].size() > crypto::chacha::TAG_SIZE, "Invalid signature size"); + std::string nonce = compute_sealing_key(ack->opening_key(), i, true); + std::string key = compute_sealing_key(ack->opening_key(), i, false); + size_t plen = m_ct.signatures[i].size() - crypto::chacha::TAG_SIZE; + std::unique_ptr<uint8_t[]> plaintext(new uint8_t[plen]); + uint8_t * buff = plaintext.get(); + + protocol::crypto::chacha::decrypt( + m_ct.signatures[i].data(), + m_ct.signatures[i].size(), + reinterpret_cast<const uint8_t *>(key.data()), + reinterpret_cast<const uint8_t *>(nonce.data()), + reinterpret_cast<char *>(buff), &plen); + m_ct.signatures[i].assign(reinterpret_cast<const char *>(buff), plen); } - if (m_ct.rv->type == rct::RCTTypeCLSAG){ - m_ct.rv->p.CLSAGs.reserve(m_ct.signatures.size()); - for (size_t i = 0; i < m_ct.signatures.size(); ++i) { - rct::clsag clsag; - if (!cn_deserialize(m_ct.signatures[i], clsag)) { - throw exc::ProtocolException("Cannot deserialize clsag[i]"); - } - m_ct.rv->p.CLSAGs.push_back(clsag); - } - } else { - m_ct.rv->p.MGs.reserve(m_ct.signatures.size()); - for (size_t i = 0; i < m_ct.signatures.size(); ++i) { - rct::mgSig mg; - if (!cn_deserialize(m_ct.signatures[i], mg)) { - throw exc::ProtocolException("Cannot deserialize mg[i]"); - } - m_ct.rv->p.MGs.push_back(mg); + m_ct.rv->p.CLSAGs.reserve(m_ct.signatures.size()); + for (size_t i = 0; i < m_ct.signatures.size(); ++i) { + rct::clsag clsag; + if (!cn_deserialize(m_ct.signatures[i], clsag)) { + throw exc::ProtocolException("Cannot deserialize clsag[i]"); } + m_ct.rv->p.CLSAGs.push_back(clsag); } m_ct.tx.rct_signatures = *(m_ct.rv); diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp index 858db1520..e9d84ace8 100644 --- a/src/device_trezor/trezor/protocol.hpp +++ b/src/device_trezor/trezor/protocol.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -116,8 +116,7 @@ namespace ki { */ bool key_image_data(wallet_shim * wallet, const std::vector<tools::wallet2::transfer_details> & transfers, - std::vector<MoneroTransferDetails> & res, - bool need_all_additionals=false); + std::vector<MoneroTransferDetails> & res); /** * Computes a hash over MoneroTransferDetails. Commitment used in the KI sync. @@ -129,8 +128,7 @@ namespace ki { */ void generate_commitment(std::vector<MoneroTransferDetails> & mtds, const std::vector<tools::wallet2::transfer_details> & transfers, - std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req, - bool need_subaddr_indices=false); + std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req); /** * Processes Live refresh step response, parses KI, checks the signature @@ -166,7 +164,7 @@ namespace tx { ::crypto::secret_key compute_enc_key(const ::crypto::secret_key & private_view_key, const std::string & aux, const std::string & salt); std::string compute_sealing_key(const std::string & master_key, size_t idx, bool is_iv=false); - typedef boost::variant<rct::rangeSig, rct::Bulletproof> rsig_v; + typedef boost::variant<rct::Bulletproof, rct::BulletproofPlus> rsig_v; /** * Transaction signer state holder. @@ -232,8 +230,8 @@ namespace tx { } const tools::wallet2::transfer_details & get_transfer(size_t idx) const { - CHECK_AND_ASSERT_THROW_MES(idx < m_unsigned_tx->transfers.second.size() + m_unsigned_tx->transfers.first && idx >= m_unsigned_tx->transfers.first, "Invalid transfer index"); - return m_unsigned_tx->transfers.second[idx - m_unsigned_tx->transfers.first]; + CHECK_AND_ASSERT_THROW_MES(idx < std::get<2>(m_unsigned_tx->transfers).size() + std::get<0>(m_unsigned_tx->transfers) && idx >= std::get<0>(m_unsigned_tx->transfers), "Invalid transfer index"); + return std::get<2>(m_unsigned_tx->transfers)[idx - std::get<0>(m_unsigned_tx->transfers)]; } const tools::wallet2::transfer_details & get_source_transfer(size_t idx) const { @@ -247,7 +245,7 @@ namespace tx { void compute_integrated_indices(TsxData * tsx_data); bool should_compute_bp_now() const; void compute_bproof(messages::monero::MoneroTransactionRsigData & rsig_data); - void process_bproof(rct::Bulletproof & bproof); + void process_bproof(rsig_v & bproof); void set_tx_input(MoneroTransactionSourceEntry * dst, size_t idx, bool need_ring_keys=false, bool need_ring_indices=false); public: @@ -260,8 +258,6 @@ namespace tx { void step_set_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetInputAck> ack); void sort_ki(); - std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> step_permutation(); - void step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack); std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> step_set_vini_input(size_t idx); void step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack); @@ -290,11 +286,15 @@ namespace tx { return m_client_version; } - bool is_simple() const { + uint8_t get_rv_type() const { if (!m_ct.rv){ throw std::invalid_argument("RV not initialized"); } - auto tp = m_ct.rv->type; + return m_ct.rv->type; + } + + bool is_simple() const { + auto tp = get_rv_type(); return tp == rct::RCTTypeSimple; } @@ -302,12 +302,27 @@ namespace tx { return m_ct.tx_data.rct_config.range_proof_type != rct::RangeProofBorromean; } + bool is_req_clsag() const { + return is_req_bulletproof() && m_ct.tx_data.rct_config.bp_version >= 3; + } + + bool is_req_bulletproof_plus() const { + return is_req_bulletproof() && m_ct.tx_data.rct_config.bp_version == 4; // rct::genRctSimple + } + bool is_bulletproof() const { - if (!m_ct.rv){ - throw std::invalid_argument("RV not initialized"); - } - auto tp = m_ct.rv->type; - return tp == rct::RCTTypeBulletproof || tp == rct::RCTTypeBulletproof2 || tp == rct::RCTTypeCLSAG; + auto tp = get_rv_type(); + return rct::is_rct_bulletproof(tp) || rct::is_rct_bulletproof_plus(tp); + } + + bool is_bulletproof_plus() const { + auto tp = get_rv_type(); + return rct::is_rct_bulletproof_plus(tp); + } + + bool is_clsag() const { + auto tp = get_rv_type(); + return rct::is_rct_clsag(tp); } bool is_offloading() const { diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp index 53b35a37a..8c6b30787 100644 --- a/src/device_trezor/trezor/transport.cpp +++ b/src/device_trezor/trezor/transport.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/transport.hpp b/src/device_trezor/trezor/transport.hpp index a452724da..3f280b104 100644 --- a/src/device_trezor/trezor/transport.hpp +++ b/src/device_trezor/trezor/transport.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/device_trezor/trezor/trezor_defs.hpp b/src/device_trezor/trezor/trezor_defs.hpp index 53bf2b03c..8e7f89031 100644 --- a/src/device_trezor/trezor/trezor_defs.hpp +++ b/src/device_trezor/trezor/trezor_defs.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/gen_multisig/CMakeLists.txt b/src/gen_multisig/CMakeLists.txt index e8aaec62c..e88d21902 100644 --- a/src/gen_multisig/CMakeLists.txt +++ b/src/gen_multisig/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2022, The Monero Project +# Copyright (c) 2017-2023, The Monero Project # # All rights reserved. # diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index f13e74b0f..48cf818ef 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -50,7 +50,6 @@ using namespace std; using namespace epee; using namespace cryptonote; -using boost::lexical_cast; namespace po = boost::program_options; #undef MONERO_DEFAULT_LOG_CATEGORY @@ -84,6 +83,9 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str try { + if (total == 0) + throw std::runtime_error("Signer group of size 0 is not allowed."); + // create M wallets first std::vector<boost::shared_ptr<tools::wallet2>> wallets(total); for (size_t n = 0; n < total; ++n) @@ -118,13 +120,17 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str ss << " " << name << std::endl; } - //exchange keys unless exchange_multisig_keys returns no extra info - while (!kex_msgs_intermediate[0].empty()) + // exchange keys until the wallets are done + bool ready{false}; + wallets[0]->multisig(&ready); + while (!ready) { for (size_t n = 0; n < total; ++n) { kex_msgs_intermediate[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), kex_msgs_intermediate); } + + wallets[0]->multisig(&ready); } std::string address = wallets[0]->get_account().get_public_address_str(wallets[0]->nettype()); diff --git a/src/gen_ssl_cert/CMakeLists.txt b/src/gen_ssl_cert/CMakeLists.txt index efadc7c31..e86518844 100644 --- a/src/gen_ssl_cert/CMakeLists.txt +++ b/src/gen_ssl_cert/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2022, The Monero Project +# Copyright (c) 2017-2023, The Monero Project # # All rights reserved. # diff --git a/src/gen_ssl_cert/gen_ssl_cert.cpp b/src/gen_ssl_cert/gen_ssl_cert.cpp index cd810ed20..e695df727 100644 --- a/src/gen_ssl_cert/gen_ssl_cert.cpp +++ b/src/gen_ssl_cert/gen_ssl_cert.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/hardforks/CMakeLists.txt b/src/hardforks/CMakeLists.txt index 81a3d694b..20ea907c5 100644 --- a/src/hardforks/CMakeLists.txt +++ b/src/hardforks/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 336ef6519..3d1d53589 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/hardforks/hardforks.h b/src/hardforks/hardforks.h index 53f14b8eb..fbe0d630d 100644 --- a/src/hardforks/hardforks.h +++ b/src/hardforks/hardforks.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/lmdb/CMakeLists.txt b/src/lmdb/CMakeLists.txt index a26c48ad5..50e08be73 100644 --- a/src/lmdb/CMakeLists.txt +++ b/src/lmdb/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/lmdb/database.cpp b/src/lmdb/database.cpp index 544197d57..22569fcf0 100644 --- a/src/lmdb/database.cpp +++ b/src/lmdb/database.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/database.h b/src/lmdb/database.h index 0c2390652..0f12571da 100644 --- a/src/lmdb/database.h +++ b/src/lmdb/database.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // All rights reserved. // diff --git a/src/lmdb/error.cpp b/src/lmdb/error.cpp index 62fdb83c3..28659dddd 100644 --- a/src/lmdb/error.cpp +++ b/src/lmdb/error.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/error.h b/src/lmdb/error.h index f4134359b..eaacc56e6 100644 --- a/src/lmdb/error.h +++ b/src/lmdb/error.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are diff --git a/src/lmdb/key_stream.h b/src/lmdb/key_stream.h index 11fa284dd..1765c0e39 100644 --- a/src/lmdb/key_stream.h +++ b/src/lmdb/key_stream.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // All rights reserved. // diff --git a/src/lmdb/table.cpp b/src/lmdb/table.cpp index 725a1a0b7..239e80ef9 100644 --- a/src/lmdb/table.cpp +++ b/src/lmdb/table.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // All rights reserved. // diff --git a/src/lmdb/transaction.h b/src/lmdb/transaction.h index f358290ec..4cb43aead 100644 --- a/src/lmdb/transaction.h +++ b/src/lmdb/transaction.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // All rights reserved. // diff --git a/src/lmdb/util.h b/src/lmdb/util.h index c6c75bc00..95057fe8d 100644 --- a/src/lmdb/util.h +++ b/src/lmdb/util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // All rights reserved. // diff --git a/src/lmdb/value_stream.cpp b/src/lmdb/value_stream.cpp index 6a1e054c1..cccb74bc3 100644 --- a/src/lmdb/value_stream.cpp +++ b/src/lmdb/value_stream.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // All rights reserved. // diff --git a/src/lmdb/value_stream.h b/src/lmdb/value_stream.h index bd2814ef4..858a54f3e 100644 --- a/src/lmdb/value_stream.h +++ b/src/lmdb/value_stream.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // All rights reserved. // diff --git a/src/mnemonics/CMakeLists.txt b/src/mnemonics/CMakeLists.txt index 738633ef5..23e63dfa1 100644 --- a/src/mnemonics/CMakeLists.txt +++ b/src/mnemonics/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/mnemonics/chinese_simplified.h b/src/mnemonics/chinese_simplified.h index 2661b5820..79f211512 100644 --- a/src/mnemonics/chinese_simplified.h +++ b/src/mnemonics/chinese_simplified.h @@ -21,7 +21,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-// Code surrounding the word list is Copyright (c) 2014-2022, The Monero Project
+// Code surrounding the word list is Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/dutch.h b/src/mnemonics/dutch.h index ace2e1b3d..afa856f3b 100644 --- a/src/mnemonics/dutch.h +++ b/src/mnemonics/dutch.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project
+// Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index b53f3acd3..69816c43c 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/mnemonics/electrum-words.h b/src/mnemonics/electrum-words.h index becd6bb4e..ab6a4a9f1 100644 --- a/src/mnemonics/electrum-words.h +++ b/src/mnemonics/electrum-words.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/mnemonics/english.h b/src/mnemonics/english.h index 1715445d8..3abb5d3b2 100644 --- a/src/mnemonics/english.h +++ b/src/mnemonics/english.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project
+// Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/english_old.h b/src/mnemonics/english_old.h index c609dff2d..71ed862cb 100644 --- a/src/mnemonics/english_old.h +++ b/src/mnemonics/english_old.h @@ -1,6 +1,6 @@ // Word list originally created as part of the Electrum project, Copyright (C) 2014 Thomas Voegtlin
//
-// Copyright (c) 2014-2022, The Monero Project
+// Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/esperanto.h b/src/mnemonics/esperanto.h index 1d3437fad..db8ccc99a 100644 --- a/src/mnemonics/esperanto.h +++ b/src/mnemonics/esperanto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project
+// Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/french.h b/src/mnemonics/french.h index be2f8957c..e39bd5902 100644 --- a/src/mnemonics/french.h +++ b/src/mnemonics/french.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/mnemonics/german.h b/src/mnemonics/german.h index 4f5265888..845bc729a 100644 --- a/src/mnemonics/german.h +++ b/src/mnemonics/german.h @@ -1,6 +1,6 @@ // Word list created by Monero contributor Shrikez
//
-// Copyright (c) 2014-2022, The Monero Project
+// Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/italian.h b/src/mnemonics/italian.h index dbd39984b..e258811d3 100644 --- a/src/mnemonics/italian.h +++ b/src/mnemonics/italian.h @@ -1,6 +1,6 @@ // Word list created by Monero contributor Shrikez
//
-// Copyright (c) 2014-2022, The Monero Project
+// Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/japanese.h b/src/mnemonics/japanese.h index 5831b1995..65d81daf1 100644 --- a/src/mnemonics/japanese.h +++ b/src/mnemonics/japanese.h @@ -21,7 +21,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-// Code surrounding the word list is Copyright (c) 2014-2022, The Monero Project
+// Code surrounding the word list is Copyright (c) 2014-2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
diff --git a/src/mnemonics/language_base.h b/src/mnemonics/language_base.h index 92eb09f0d..f6ed5bd57 100644 --- a/src/mnemonics/language_base.h +++ b/src/mnemonics/language_base.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project
+// Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/lojban.h b/src/mnemonics/lojban.h index 68aefd5fd..9c127c0a0 100644 --- a/src/mnemonics/lojban.h +++ b/src/mnemonics/lojban.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project
+// Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/portuguese.h b/src/mnemonics/portuguese.h index 9ddac09bb..f87464b27 100644 --- a/src/mnemonics/portuguese.h +++ b/src/mnemonics/portuguese.h @@ -21,7 +21,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-// Code surrounding the word list is Copyright (c) 2014-2022, The Monero Project
+// Code surrounding the word list is Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/russian.h b/src/mnemonics/russian.h index 8922b1ed9..23b108daa 100644 --- a/src/mnemonics/russian.h +++ b/src/mnemonics/russian.h @@ -1,6 +1,6 @@ // Word list created by Monero contributor sammy007
//
-// Copyright (c) 2014-2022, The Monero Project
+// Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/singleton.h b/src/mnemonics/singleton.h index 91faad92c..156db9e3d 100644 --- a/src/mnemonics/singleton.h +++ b/src/mnemonics/singleton.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project
+// Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/mnemonics/spanish.h b/src/mnemonics/spanish.h index 1bdb6b934..312ac4adc 100644 --- a/src/mnemonics/spanish.h +++ b/src/mnemonics/spanish.h @@ -21,7 +21,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-// Code surrounding the word list is Copyright (c) 2014-2022, The Monero Project
+// Code surrounding the word list is Copyright (c) 2014-2023, The Monero Project
//
// All rights reserved.
//
diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt index 61e658a39..62ae00e80 100644 --- a/src/multisig/CMakeLists.txt +++ b/src/multisig/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2022, The Monero Project +# Copyright (c) 2017-2023, The Monero Project # # All rights reserved. # diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index fabffdd02..70baa7f19 100644 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h index 16dbbc544..2ec2f4e66 100644 --- a/src/multisig/multisig.h +++ b/src/multisig/multisig.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_account.cpp b/src/multisig/multisig_account.cpp index 9bdcf2dbc..4f9711b15 100644 --- a/src/multisig/multisig_account.cpp +++ b/src/multisig/multisig_account.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022, The Monero Project +// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // @@ -127,7 +127,7 @@ namespace multisig bool multisig_account::multisig_is_ready() const { if (main_kex_rounds_done()) - return m_kex_rounds_complete >= multisig_kex_rounds_required(m_signers.size(), m_threshold) + 1; + return m_kex_rounds_complete >= multisig_setup_rounds_required(m_signers.size(), m_threshold); else return false; } @@ -175,19 +175,20 @@ namespace multisig // only mutate account if update succeeds multisig_account temp_account{*this}; temp_account.set_multisig_config(threshold, std::move(signers)); - temp_account.kex_update_impl(expanded_msgs_rnd1); + temp_account.kex_update_impl(expanded_msgs_rnd1, false); *this = std::move(temp_account); } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL //---------------------------------------------------------------------------------------------------------------------- - void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs) + void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const bool force_update_use_with_caution /*= false*/) { CHECK_AND_ASSERT_THROW_MES(account_is_active(), "multisig account: tried to update kex, but kex isn't initialized yet."); CHECK_AND_ASSERT_THROW_MES(!multisig_is_ready(), "multisig account: tried to update kex, but kex is already complete."); multisig_account temp_account{*this}; - temp_account.kex_update_impl(expanded_msgs); + temp_account.kex_update_impl(expanded_msgs, force_update_use_with_caution); *this = std::move(temp_account); } //---------------------------------------------------------------------------------------------------------------------- @@ -200,4 +201,11 @@ namespace multisig return num_signers - threshold + 1; } //---------------------------------------------------------------------------------------------------------------------- + // EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + std::uint32_t multisig_setup_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold) + { + return multisig_kex_rounds_required(num_signers, threshold) + 1; + } + //---------------------------------------------------------------------------------------------------------------------- } //namespace multisig diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h index 7b372bbff..0d832f243 100644 --- a/src/multisig/multisig_account.h +++ b/src/multisig/multisig_account.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022, The Monero Project +// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // @@ -169,12 +169,20 @@ namespace multisig * - The main interface for multisig key exchange, this handles all the work of processing input messages, * creating new messages for new rounds, and finalizing the multisig shared public key when kex is complete. * param: expanded_msgs - kex messages corresponding to the account's 'in progress' round + * param: force_update_use_with_caution - try to force the account to update with messages from an incomplete signer set. + * - If this is the post-kex verification round, only require one input message. + * - Force updating here should only be done if we can safely assume an honest signer subgroup of size 'threshold' + * will complete the account. + * - If this is an intermediate round, only require messages from 'num signers - 1 - (round - 1)' other signers. + * - If force updating with maliciously-crafted messages, the resulting account will be invalid (either unable + * to complete signatures, or a 'hostage' to the malicious signer [i.e. can't sign without his participation]). */ - void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs); + void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const bool force_update_use_with_caution = false); private: // implementation of kex_update() (non-transactional) - void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs); + void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs, const bool incomplete_signer_set); /** * brief: initialize_kex_update - Helper for kex_update_impl() * - Collect the local signer's shared keys to ignore in incoming messages, build the aggregate ancillary key @@ -245,4 +253,13 @@ namespace multisig * return: number of kex rounds required */ std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold); + + /** + * brief: multisig_setup_rounds_required - The number of setup rounds required to produce an M-of-N shared key. + * - A participant must complete all kex rounds and 1 initialization round. + * param: num_signers - number of participants in multisig (N) + * param: threshold - threshold of multisig (M) + * return: number of setup rounds required + */ + std::uint32_t multisig_setup_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold); } //namespace multisig diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp index be9ed9cb2..ef0acf307 100644 --- a/src/multisig/multisig_account_kex_impl.cpp +++ b/src/multisig/multisig_account_kex_impl.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022, The Monero Project +// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // @@ -74,7 +74,7 @@ namespace multisig "Multisig threshold may not be larger than number of signers."); CHECK_AND_ASSERT_THROW_MES(threshold > 0, "Multisig threshold must be > 0."); CHECK_AND_ASSERT_THROW_MES(round > 0, "Multisig kex round must be > 0."); - CHECK_AND_ASSERT_THROW_MES(round <= multisig_kex_rounds_required(num_signers, threshold) + 1, + CHECK_AND_ASSERT_THROW_MES(round <= multisig_setup_rounds_required(num_signers, threshold), "Trying to process multisig kex for an invalid round."); } //---------------------------------------------------------------------------------------------------------------------- @@ -181,7 +181,8 @@ namespace multisig * Key aggregation via aggregation coefficients prevents key cancellation attacks. * See: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf * param: final_keys - address components (public keys) obtained from other participants (not shared with local) - * param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation coefficient (return by reference) + * param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation + * coefficient (return by reference) * return: final multisig public spend key for the account */ //---------------------------------------------------------------------------------------------------------------------- @@ -199,7 +200,8 @@ namespace multisig for (std::size_t multisig_keys_index{0}; multisig_keys_index < privkeys_inout.size(); ++multisig_keys_index) { crypto::public_key pubkey; - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey), "Failed to derive public key"); + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey), + "Failed to derive public key"); own_keys_mapping[pubkey] = multisig_keys_index; @@ -307,8 +309,7 @@ namespace multisig * INTERNAL * * brief: multisig_kex_msgs_sanitize_pubkeys - Sanitize multisig kex messages. - * - Removes duplicates from msg pubkeys, ignores pubkeys equal to the local account's signing key, - * ignores messages signed by the local account, ignores keys found in input 'exclusion set', + * - Removes duplicates from msg pubkeys, ignores keys found in input 'exclusion set', * constructs map of pubkey:origins. * - Requires that all input msgs have the same round number. * @@ -316,15 +317,13 @@ namespace multisig * * - If the messages' round numbers are all '1', then only the message signing pubkey is considered * 'recommended'. Furthermore, the 'exclusion set' is ignored. - * param: own_pubkey - local account's signing key (key used to sign multisig messages) * param: expanded_msgs - set of multisig kex messages to process * param: exclude_pubkeys - pubkeys to exclude from output set * outparam: sanitized_pubkeys_out - processed pubkeys obtained from msgs, mapped to their origins * return: round number shared by all input msgs */ //---------------------------------------------------------------------------------------------------------------------- - static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const crypto::public_key &own_pubkey, - const std::vector<multisig_kex_msg> &expanded_msgs, + static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const std::vector<multisig_kex_msg> &expanded_msgs, const std::vector<crypto::public_key> &exclude_pubkeys, multisig_keyset_map_memsafe_t &sanitized_pubkeys_out) { @@ -339,10 +338,6 @@ namespace multisig // - origins = all the signing pubkeys that recommended a given msg pubkey for (const auto &expanded_msg : expanded_msgs) { - // ignore messages from self - if (expanded_msg.get_signing_pubkey() == own_pubkey) - continue; - // in round 1, only the signing pubkey is treated as a msg pubkey if (round == 1) { @@ -355,10 +350,6 @@ namespace multisig // copy all pubkeys from message into list for (const auto &pubkey : expanded_msg.get_msg_pubkeys()) { - // ignore own pubkey - if (pubkey == own_pubkey) - continue; - // ignore pubkeys in 'ignore' set if (std::find(exclude_pubkeys.begin(), exclude_pubkeys.end(), pubkey) != exclude_pubkeys.end()) continue; @@ -375,6 +366,31 @@ namespace multisig /** * INTERNAL * + * brief: remove_key_from_mapped_sets - Remove a specified key from the mapped sets in a multisig keyset map. + * param: key_to_remove - specified key to remove + * inoutparam: keyset_inout - keyset to update + */ + //---------------------------------------------------------------------------------------------------------------------- + static void remove_key_from_mapped_sets(const crypto::public_key &key_to_remove, + multisig_keyset_map_memsafe_t &keyset_inout) + { + // remove specified key from each mapped set + for (auto keyset_it = keyset_inout.begin(); keyset_it != keyset_inout.end();) + { + // remove specified key from this set + keyset_it->second.erase(key_to_remove); + + // remove empty keyset positions or increment iterator + if (keyset_it->second.size() == 0) + keyset_it = keyset_inout.erase(keyset_it); + else + ++keyset_it; + } + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * * brief: evaluate_multisig_kex_round_msgs - Evaluate pubkeys from a kex round in order to prepare for the next round. * - Sanitizes input msgs. * - Require uniqueness in: 'exclude_pubkeys'. @@ -392,6 +408,8 @@ namespace multisig * param: signers - expected participants in multisig kex * param: expanded_msgs - set of multisig kex messages to process * param: exclude_pubkeys - derivations held by the local account corresponding to round 'expected_round' + * param: incomplete_signer_set - only require the minimum number of signers to complete this round + * minimum = num_signers - (round num - 1) (including local signer) * return: fully sanitized and validated pubkey:origins map for building the account's next kex round message */ //---------------------------------------------------------------------------------------------------------------------- @@ -400,7 +418,8 @@ namespace multisig const std::uint32_t expected_round, const std::vector<crypto::public_key> &signers, const std::vector<multisig_kex_msg> &expanded_msgs, - const std::vector<crypto::public_key> &exclude_pubkeys) + const std::vector<crypto::public_key> &exclude_pubkeys, + const bool incomplete_signer_set) { // exclude_pubkeys should all be unique for (auto it = exclude_pubkeys.begin(); it != exclude_pubkeys.end(); ++it) @@ -410,21 +429,31 @@ namespace multisig } // sanitize input messages - multisig_keyset_map_memsafe_t pubkey_origins_map; - const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, exclude_pubkeys, pubkey_origins_map); + multisig_keyset_map_memsafe_t pubkey_origins_map; //map: [pubkey : [origins]] + const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(expanded_msgs, exclude_pubkeys, pubkey_origins_map); CHECK_AND_ASSERT_THROW_MES(round == expected_round, "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]"); + // remove the local signer from each origins set in the sanitized pubkey map + // note: intermediate kex rounds only need keys from other signers to make progress (keys from self are useless) + remove_key_from_mapped_sets(base_pubkey, pubkey_origins_map); + // evaluate pubkeys collected - std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map; + std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map; //map: [origin: [pubkeys]] // 1. each pubkey should be recommended by a precise number of signers + const std::size_t num_recommendations_per_pubkey_required{ + incomplete_signer_set + ? 1 + : round + }; + for (const auto &pubkey_and_origins : pubkey_origins_map) { // expected amount = round_num // With each successive round, pubkeys are shared by incrementally larger groups, // starting at 1 in round 1 (i.e. the local multisig key to start kex with). - CHECK_AND_ASSERT_THROW_MES(pubkey_and_origins.second.size() == round, + CHECK_AND_ASSERT_THROW_MES(pubkey_and_origins.second.size() >= num_recommendations_per_pubkey_required, "A pubkey recommended by multisig kex messages had an unexpected number of recommendations."); // map (sanitized) pubkeys back to origins @@ -433,8 +462,18 @@ namespace multisig } // 2. the number of unique signers recommending pubkeys should equal the number of signers passed in (minus the local signer) - CHECK_AND_ASSERT_THROW_MES(origin_pubkeys_map.size() == signers.size() - 1, - "Number of unique other signers does not equal number of other signers that recommended pubkeys."); + // - if an incomplete set is allowed, then we need at least one signer to represent each subgroup in this round that + // doesn't include the local signer + const std::size_t num_signers_required{ + incomplete_signer_set + ? signers.size() - 1 - (round - 1) + : signers.size() - 1 + }; + + CHECK_AND_ASSERT_THROW_MES(origin_pubkeys_map.size() >= num_signers_required, + "Number of unique other signers recommending pubkeys does not equal number of required other signers " + "(kex round: " << round << ", num signers found: " << origin_pubkeys_map.size() << ", num signers required: " << + num_signers_required << ")."); // 3. each origin should recommend a precise number of pubkeys @@ -461,19 +500,20 @@ namespace multisig // other signers: (N - 2) choose (msg_round_num - 1) // - Each signer recommends keys they share with other signers. - // - In each round, a signer shares a key with 'round num - 1' other signers. - // - Since 'origins pubkey map' excludes keys shared with the local account, - // only keys shared with participants 'other than local and self' will be in the map (e.g. N - 2 signers). - // - So other signers will recommend (N - 2) choose (msg_round_num - 1) pubkeys (after removing keys shared with local). - // - Each origin should have a shared key with each group of size 'round - 1'. - // Note: Keys shared with local are ignored to facilitate kex round boosting, where one or more signers may + // - In each round, every group of size 'round num' will have a key. From a single signer's perspective, + // they will share a key with every group of size 'round num - 1' of other signers. + // - Since 'origins pubkey map' excludes keys shared with the local account, only keys shared with participants + // 'other than local and self' will be in the map (e.g. N - 2 signers). + // - Other signers will recommend (N - 2) choose (msg_round_num - 1) pubkeys (after removing keys shared with local). + // Note: Keys shared with local are filtered out to facilitate kex round boosting, where one or more signers may // have boosted the local signer (implying they didn't have access to the local signer's previous round msg). const std::uint32_t expected_recommendations_others = n_choose_k_f(signers.size() - 2, round - 1); // local: (N - 1) choose (msg_round_num - 1) const std::uint32_t expected_recommendations_self = n_choose_k_f(signers.size() - 1, round - 1); - // note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we return early for that case + // note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we don't call this function for + // that case CHECK_AND_ASSERT_THROW_MES(expected_recommendations_self > 0 && expected_recommendations_others > 0, "Bad num signers or round num (possibly numerical limits exceeded)."); @@ -485,7 +525,7 @@ namespace multisig for (const auto &origin_and_pubkeys : origin_pubkeys_map) { CHECK_AND_ASSERT_THROW_MES(origin_and_pubkeys.second.size() == expected_recommendations_others, - "A pubkey recommended by multisig kex messages had an unexpected number of recommendations."); + "A multisig signer recommended an unexpected number of pubkeys."); // 2 (continued). only expected signers should be recommending keys CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin_and_pubkeys.first) != signers.end(), @@ -507,6 +547,7 @@ namespace multisig * param: expected_round - expected kex round of input messages * param: signers - expected participants in multisig kex * param: expanded_msgs - set of multisig kex messages to process + * param: incomplete_signer_set - only require the minimum amount of messages to complete this round (1 message) * return: sanitized and validated pubkey:origins map */ //---------------------------------------------------------------------------------------------------------------------- @@ -514,15 +555,20 @@ namespace multisig const crypto::public_key &base_pubkey, const std::uint32_t expected_round, const std::vector<crypto::public_key> &signers, - const std::vector<multisig_kex_msg> &expanded_msgs) + const std::vector<multisig_kex_msg> &expanded_msgs, + const bool incomplete_signer_set) { // sanitize input messages const std::vector<crypto::public_key> dummy; - multisig_keyset_map_memsafe_t pubkey_origins_map; - const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, dummy, pubkey_origins_map); + multisig_keyset_map_memsafe_t pubkey_origins_map; //map: [pubkey : [origins]] + const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(expanded_msgs, dummy, pubkey_origins_map); CHECK_AND_ASSERT_THROW_MES(round == expected_round, "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]"); + // note: do NOT remove the local signer from the pubkey origins map, since the post-kex round can be force-updated with + // just the local signer's post-kex message (if the local signer were removed, then the post-kex message's pubkeys + // would be completely lost) + // evaluate pubkeys collected // 1) there should only be two pubkeys @@ -533,17 +579,26 @@ namespace multisig CHECK_AND_ASSERT_THROW_MES(pubkey_origins_map.begin()->second == (++(pubkey_origins_map.begin()))->second, "Multisig post-kex round messages from other signers did not all recommend the same pubkey pair."); - // 3) all signers should be present in the recommendation list + // 3) all signers should be present in the recommendation list (unless an incomplete list is permitted) auto origins = pubkey_origins_map.begin()->second; - origins.insert(base_pubkey); //add self + origins.insert(base_pubkey); //add self if missing - CHECK_AND_ASSERT_THROW_MES(origins.size() == signers.size(), - "Multisig post-kex round message origins don't line up with multisig signer set."); + const std::size_t num_signers_required{ + incomplete_signer_set + ? 1 + : signers.size() + }; + + CHECK_AND_ASSERT_THROW_MES(origins.size() >= num_signers_required, + "Multisig post-kex round message origins don't line up with multisig signer set " + "(num signers found: " << origins.size() << ", num signers required: " << num_signers_required << ")."); - for (const crypto::public_key &signer : signers) + for (const crypto::public_key &origin : origins) { - CHECK_AND_ASSERT_THROW_MES(origins.find(signer) != origins.end(), - "Could not find an expected signer in multisig post-kex round messages (all signers expected)."); + // note: if num_signers_required == signers.size(), then this test will ensure all signers are present in 'origins', + // which contains only unique pubkeys + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin) != signers.end(), + "An unknown origin recommended a multisig post-kex verification messsage."); } return pubkey_origins_map; @@ -564,6 +619,7 @@ namespace multisig * param: expanded_msgs - set of multisig kex messages to process * param: exclude_pubkeys - keys held by the local account corresponding to round 'current_round' * - If 'current_round' is the final round, these are the local account's shares of the final aggregate key. + * param: incomplete_signer_set - allow messages from an incomplete signer set * outparam: keys_to_origins_map_out - map between round keys and identity keys * - If in the final round, these are key shares recommended by other signers for the final aggregate key. * - Otherwise, these are the local account's DH derivations for the next round. @@ -578,6 +634,7 @@ namespace multisig const std::vector<crypto::public_key> &signers, const std::vector<multisig_kex_msg> &expanded_msgs, const std::vector<crypto::public_key> &exclude_pubkeys, + const bool incomplete_signer_set, multisig_keyset_map_memsafe_t &keys_to_origins_map_out) { check_multisig_config(current_round, threshold, signers.size()); @@ -598,7 +655,8 @@ namespace multisig current_round, signers, expanded_msgs, - exclude_pubkeys); + exclude_pubkeys, + incomplete_signer_set); } else //(current_round == kex_rounds_required + 1) { @@ -606,7 +664,8 @@ namespace multisig evaluated_pubkeys = evaluate_multisig_post_kex_round_msgs(base_pubkey, current_round, signers, - expanded_msgs); + expanded_msgs, + incomplete_signer_set); } // prepare keys-to-origins map for updating the multisig account @@ -693,9 +752,9 @@ namespace multisig { // post-kex verification round: check that the multisig pubkey and common pubkey were recommended by other signers CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.count(m_multisig_pubkey) > 0, - "Multisig post-kex round: expected multisig pubkey wasn't found in other signers' messages."); + "Multisig post-kex round: expected multisig pubkey wasn't found in input messages."); CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.count(m_common_pubkey) > 0, - "Multisig post-kex round: expected common pubkey wasn't found in other signers' messages."); + "Multisig post-kex round: expected common pubkey wasn't found in input messages."); // save keys that should be recommended to other signers // - for convenience, re-recommend the post-kex verification message once an account is complete @@ -790,7 +849,8 @@ namespace multisig //---------------------------------------------------------------------------------------------------------------------- // multisig_account: INTERNAL //---------------------------------------------------------------------------------------------------------------------- - void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs) + void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs, + const bool incomplete_signer_set) { // check messages are for the expected kex round check_messages_round(expanded_msgs, m_kex_rounds_complete + 1); @@ -816,6 +876,7 @@ namespace multisig m_signers, expanded_msgs, exclude_pubkeys, + incomplete_signer_set, result_keys_to_origins_map); // finish account update diff --git a/src/multisig/multisig_clsag_context.cpp b/src/multisig/multisig_clsag_context.cpp index e3417b896..81e471287 100644 --- a/src/multisig/multisig_clsag_context.cpp +++ b/src/multisig/multisig_clsag_context.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021, The Monero Project +// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_clsag_context.h b/src/multisig/multisig_clsag_context.h index 5017e8688..c184b83d6 100644 --- a/src/multisig/multisig_clsag_context.h +++ b/src/multisig/multisig_clsag_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021, The Monero Project +// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_kex_msg.cpp b/src/multisig/multisig_kex_msg.cpp index c717e23ad..49350948a 100644 --- a/src/multisig/multisig_kex_msg.cpp +++ b/src/multisig/multisig_kex_msg.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022, The Monero Project +// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_kex_msg.h b/src/multisig/multisig_kex_msg.h index 833c7c8f6..8cb8faa7c 100644 --- a/src/multisig/multisig_kex_msg.h +++ b/src/multisig/multisig_kex_msg.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022, The Monero Project +// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_kex_msg_serialization.h b/src/multisig/multisig_kex_msg_serialization.h index e5558cdff..d1e07ea8a 100644 --- a/src/multisig/multisig_kex_msg_serialization.h +++ b/src/multisig/multisig_kex_msg_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022, The Monero Project +// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp index e5c9ac483..8643a8af4 100644 --- a/src/multisig/multisig_tx_builder_ringct.cpp +++ b/src/multisig/multisig_tx_builder_ringct.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021, The Monero Project +// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // diff --git a/src/multisig/multisig_tx_builder_ringct.h b/src/multisig/multisig_tx_builder_ringct.h index 853934659..f1bd24e73 100644 --- a/src/multisig/multisig_tx_builder_ringct.h +++ b/src/multisig/multisig_tx_builder_ringct.h @@ -1,4 +1,4 @@ -// Copyright (c) 2021, The Monero Project +// Copyright (c) 2021-2023, The Monero Project // // All rights reserved. // diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt index a4d31eedf..29807f50e 100644 --- a/src/net/CMakeLists.txt +++ b/src/net/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2022, The Monero Project +# Copyright (c) 2018-2023, The Monero Project # # All rights reserved. diff --git a/src/net/dandelionpp.cpp b/src/net/dandelionpp.cpp index e75fb2753..b38427086 100644 --- a/src/net/dandelionpp.cpp +++ b/src/net/dandelionpp.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/net/dandelionpp.h b/src/net/dandelionpp.h index e344bc7ce..24e1fc625 100644 --- a/src/net/dandelionpp.h +++ b/src/net/dandelionpp.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/net/error.cpp b/src/net/error.cpp index 254db7ae1..0f3de6a6c 100644 --- a/src/net/error.cpp +++ b/src/net/error.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/net/error.h b/src/net/error.h index 969eefc41..3bce227ee 100644 --- a/src/net/error.h +++ b/src/net/error.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/net/fwd.h b/src/net/fwd.h index b105da115..871c13432 100644 --- a/src/net/fwd.h +++ b/src/net/fwd.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/net/http.cpp b/src/net/http.cpp index c0ed3d430..8eaf5b4b4 100644 --- a/src/net/http.cpp +++ b/src/net/http.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/net/http.h b/src/net/http.h index bbb8ee984..72bd3f66d 100644 --- a/src/net/http.h +++ b/src/net/http.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/net/i2p_address.cpp b/src/net/i2p_address.cpp index b0194a525..a151e420b 100644 --- a/src/net/i2p_address.cpp +++ b/src/net/i2p_address.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/net/i2p_address.h b/src/net/i2p_address.h index 7f6ef1b3f..4d342e67d 100644 --- a/src/net/i2p_address.h +++ b/src/net/i2p_address.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/net/parse.cpp b/src/net/parse.cpp index 1df6175b4..71d0d7b26 100644 --- a/src/net/parse.cpp +++ b/src/net/parse.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. @@ -38,7 +38,7 @@ namespace net { void get_network_address_host_and_port(const std::string& address, std::string& host, std::string& port) { - // require ipv6 address format "[addr:addr:addr:...:addr]:port" + // If IPv6 address format with port "[addr:addr:addr:...:addr]:port" if (address.find(']') != std::string::npos) { host = address.substr(1, address.rfind(']') - 1); @@ -47,6 +47,12 @@ namespace net port = address.substr(address.rfind(':') + 1); } } + // Else if IPv6 address format without port e.g. "addr:addr:addr:...:addr" + else if (std::count(address.begin(), address.end(), ':') >= 2) + { + host = address; + } + // Else IPv4, Tor, I2P address or hostname else { host = address.substr(0, address.rfind(':')); diff --git a/src/net/parse.h b/src/net/parse.h index 648076d7b..695049268 100644 --- a/src/net/parse.h +++ b/src/net/parse.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. @@ -38,6 +38,16 @@ namespace net { + /*! + * \brief Takes a valid address string (IP, Tor, I2P, or DNS name) and splits it into host and port + * + * The host of an IPv6 addresses in the format "[x:x:..:x]:port" will have the braces stripped. + * For example, when the address is "[ffff::2023]", host will be set to "ffff::2023". + * + * \param address The address string one wants to split + * \param[out] host The host part of the address string. Is always set. + * \param[out] port The port part of the address string. Is only set when address string contains a port. + */ void get_network_address_host_and_port(const std::string& address, std::string& host, std::string& port); /*! diff --git a/src/net/resolve.cpp b/src/net/resolve.cpp index f4881ffe5..c4c8e8d08 100644 --- a/src/net/resolve.cpp +++ b/src/net/resolve.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/net/resolve.h b/src/net/resolve.h index 39b1d1830..afbe5d879 100644 --- a/src/net/resolve.h +++ b/src/net/resolve.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/net/socks.cpp b/src/net/socks.cpp index 97ef4a672..cc8e0be6d 100644 --- a/src/net/socks.cpp +++ b/src/net/socks.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. // diff --git a/src/net/socks.h b/src/net/socks.h index af67d4abe..920496c6e 100644 --- a/src/net/socks.h +++ b/src/net/socks.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. // diff --git a/src/net/socks_connect.cpp b/src/net/socks_connect.cpp index 5317564de..f24569674 100644 --- a/src/net/socks_connect.cpp +++ b/src/net/socks_connect.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/net/socks_connect.h b/src/net/socks_connect.h index 587e3cd3c..e036d9c31 100644 --- a/src/net/socks_connect.h +++ b/src/net/socks_connect.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/net/tor_address.cpp b/src/net/tor_address.cpp index ac36dbffd..53b73a839 100644 --- a/src/net/tor_address.cpp +++ b/src/net/tor_address.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/net/tor_address.h b/src/net/tor_address.h index 5b036f0d4..3dd320b5d 100644 --- a/src/net/tor_address.h +++ b/src/net/tor_address.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/net/zmq.cpp b/src/net/zmq.cpp index 2b3ca8376..f57cbdac6 100644 --- a/src/net/zmq.cpp +++ b/src/net/zmq.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/net/zmq.h b/src/net/zmq.h index 18bb80c8b..2b60432d7 100644 --- a/src/net/zmq.h +++ b/src/net/zmq.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/p2p/CMakeLists.txt b/src/p2p/CMakeLists.txt index af58d2bb0..bd2345979 100644 --- a/src/p2p/CMakeLists.txt +++ b/src/p2p/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index f9803fd81..13ff06c8b 100644 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 98d8ecfff..cf2d212b3 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index f33ce977d..4f77ce834 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -51,7 +51,6 @@ #include "common/dns_utils.h" #include "common/pruning.h" #include "net/error.h" -#include "net/net_helper.h" #include "math_helper.h" #include "misc_log_ex.h" #include "p2p_protocol_defs.h" @@ -67,8 +66,6 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net.p2p" -#define NET_MAKE_IP(b1,b2,b3,b4) ((LPARAM)(((DWORD)(b1)<<24)+((DWORD)(b2)<<16)+((DWORD)(b3)<<8)+((DWORD)(b4)))) - #define MIN_WANTED_SEED_NODES 12 static inline boost::asio::ip::address_v4 make_address_v4_from_v6(const boost::asio::ip::address_v6& a) @@ -247,7 +244,23 @@ namespace nodetool if (it == m_blocked_hosts.end()) { m_blocked_hosts[host_str] = limit; - added = true; + + // if the host was already blocked due to being in a blocked subnet, let it be silent + bool matches_blocked_subnet = false; + if (addr.get_type_id() == epee::net_utils::address_type::ipv4) + { + auto ipv4_address = addr.template as<epee::net_utils::ipv4_network_address>(); + for (auto jt = m_blocked_subnets.begin(); jt != m_blocked_subnets.end(); ++jt) + { + if (jt->first.matches(ipv4_address)) + { + matches_blocked_subnet = true; + break; + } + } + } + if (!matches_blocked_subnet) + added = true; } else if (it->second < limit || !add_only) it->second = limit; @@ -317,6 +330,7 @@ namespace nodetool limit = std::numeric_limits<time_t>::max(); else limit = now + seconds; + const bool added = m_blocked_subnets.find(subnet) == m_blocked_subnets.end(); m_blocked_subnets[subnet] = limit; // drop any connection to that subnet. This should only have to look into @@ -349,7 +363,10 @@ namespace nodetool conns.clear(); } - MCLOG_CYAN(el::Level::Info, "global", "Subnet " << subnet.host_str() << " blocked."); + if (added) + MCLOG_CYAN(el::Level::Info, "global", "Subnet " << subnet.host_str() << " blocked."); + else + MINFO("Subnet " << subnet.host_str() << " blocked."); return true; } //----------------------------------------------------------------------------------- @@ -645,20 +662,10 @@ namespace nodetool { using namespace boost::asio; - std::string host = addr; + // Split addr string into host string and port string + std::string host; std::string port = std::to_string(default_port); - size_t colon_pos = addr.find_last_of(':'); - size_t dot_pos = addr.find_last_of('.'); - size_t square_brace_pos = addr.find('['); - - // IPv6 will have colons regardless. IPv6 and IPv4 address:port will have a colon but also either a . or a [ - // as IPv6 addresses specified as address:port are to be specified as "[addr:addr:...:addr]:port" - // One may also specify an IPv6 address as simply "[addr:addr:...:addr]" without the port; in that case - // the square braces will be stripped here. - if ((std::string::npos != colon_pos && std::string::npos != dot_pos) || std::string::npos != square_brace_pos) - { - net::get_network_address_host_and_port(addr, host, port); - } + net::get_network_address_host_and_port(addr, host, port); MINFO("Resolving node address: host=" << host << ", port=" << port); io_service io_srv; @@ -695,34 +702,32 @@ namespace nodetool std::set<std::string> full_addrs; if (m_nettype == cryptonote::TESTNET) { - full_addrs.insert("212.83.175.67:28080"); - full_addrs.insert("212.83.172.165:28080"); full_addrs.insert("176.9.0.187:28080"); full_addrs.insert("88.99.173.38:28080"); full_addrs.insert("51.79.173.165:28080"); + full_addrs.insert("192.99.8.110:28080"); + full_addrs.insert("37.187.74.171:28080"); } else if (m_nettype == cryptonote::STAGENET) { - full_addrs.insert("162.210.173.150:38080"); full_addrs.insert("176.9.0.187:38080"); full_addrs.insert("88.99.173.38:38080"); full_addrs.insert("51.79.173.165:38080"); + full_addrs.insert("192.99.8.110:38080"); + full_addrs.insert("37.187.74.171:38080"); } else if (m_nettype == cryptonote::FAKECHAIN) { } else { - full_addrs.insert("212.83.175.67:18080"); - full_addrs.insert("212.83.172.165:18080"); full_addrs.insert("176.9.0.187:18080"); full_addrs.insert("88.198.163.90:18080"); - full_addrs.insert("95.217.25.101:18080"); - full_addrs.insert("136.244.105.131:18080"); - full_addrs.insert("104.238.221.81:18080"); full_addrs.insert("66.85.74.134:18080"); full_addrs.insert("88.99.173.38:18080"); full_addrs.insert("51.79.173.165:18080"); + full_addrs.insert("192.99.8.110:18080"); + full_addrs.insert("37.187.74.171:18080"); } return full_addrs; } @@ -857,6 +862,8 @@ namespace nodetool "4pixvbejrvihnkxmduo2agsnmc3rrulrqc7s3cbwwrep6h6hrzsibeqd.onion:18083", "zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083", "qz43zul2x56jexzoqgkx2trzwcfnr6l3hbtfcfx54g4r3eahy3bssjyd.onion:18083", + "plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion:18083", + "plowsoffjexmxalw73tkjmf422gq6575fc7vicuu4javzn2ynnte6tyd.onion:18083", }; } return {}; @@ -865,7 +872,9 @@ namespace nodetool { return { "s3l6ke4ed3df466khuebb4poienoingwof7oxtbo6j4n56sghe3a.b32.i2p:18080", - "sel36x6fibfzujwvt4hf5gxolz6kd3jpvbjqg6o3ud2xtionyl2q.b32.i2p:18080" + "sel36x6fibfzujwvt4hf5gxolz6kd3jpvbjqg6o3ud2xtionyl2q.b32.i2p:18080", + "uqj3aphckqtjsitz7kxx5flqpwjlq5ppr3chazfued7xucv3nheq.b32.i2p:18080", + "vdmnehdjkpkg57nthgnjfuaqgku673r5bpbqg56ix6fyqoywgqrq.b32.i2p:18080", }; } return {}; @@ -2413,7 +2422,7 @@ namespace nodetool return false; } return true; - }); + }, "0.0.0.0", m_ssl_support); if(!r) { LOG_WARNING_CC(context, "Failed to call connect_async, network error."); diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index ef4552f44..66ef40f1e 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/p2p/net_peerlist.cpp b/src/p2p/net_peerlist.cpp index 3e132c91f..11cf235a5 100644 --- a/src/p2p/net_peerlist.cpp +++ b/src/p2p/net_peerlist.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index dc480462d..c2f700598 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index 7d8bd1480..7f4aecce3 100644 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 8b9cd0f09..18b853d35 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/platform/mingw/alloca.h b/src/platform/mingw/alloca.h index 10a9db198..8ed1c6514 100644 --- a/src/platform/mingw/alloca.h +++ b/src/platform/mingw/alloca.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/alloca.h b/src/platform/msc/alloca.h index 3b197bf6d..8c4701b78 100644 --- a/src/platform/msc/alloca.h +++ b/src/platform/msc/alloca.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/inline_c.h b/src/platform/msc/inline_c.h index 36d3d0026..72bad9223 100644 --- a/src/platform/msc/inline_c.h +++ b/src/platform/msc/inline_c.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/stdbool.h b/src/platform/msc/stdbool.h index d4932e8f8..1bfd78f57 100644 --- a/src/platform/msc/stdbool.h +++ b/src/platform/msc/stdbool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/platform/msc/sys/param.h b/src/platform/msc/sys/param.h index 61002e1a1..1f1950ac6 100644 --- a/src/platform/msc/sys/param.h +++ b/src/platform/msc/sys/param.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt index d415e6409..1f3d1913f 100644 --- a/src/ringct/CMakeLists.txt +++ b/src/ringct/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2022, The Monero Project +# Copyright (c) 2016-2023, The Monero Project # # All rights reserved. # diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index ba7ab2c0d..2cfaf18c0 100644 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/ringct/bulletproofs.h b/src/ringct/bulletproofs.h index c3fe6f8b9..7ff47e8a0 100644 --- a/src/ringct/bulletproofs.h +++ b/src/ringct/bulletproofs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/ringct/bulletproofs_plus.cc b/src/ringct/bulletproofs_plus.cc index 77b800064..0619811cb 100644 --- a/src/ringct/bulletproofs_plus.cc +++ b/src/ringct/bulletproofs_plus.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/ringct/bulletproofs_plus.h b/src/ringct/bulletproofs_plus.h index 861c54f4f..80a8de8fb 100644 --- a/src/ringct/bulletproofs_plus.h +++ b/src/ringct/bulletproofs_plus.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/ringct/multiexp.cc b/src/ringct/multiexp.cc index 67814682a..5e241f2cc 100644 --- a/src/ringct/multiexp.cc +++ b/src/ringct/multiexp.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. @@ -57,28 +57,28 @@ extern "C" // 1 1 52.8 70.4 70.2 // Pippenger: -// 1 2 3 4 5 6 7 8 9 bestN -// 2 555 598 621 804 1038 1733 2486 5020 8304 1 -// 4 783 747 800 1006 1428 2132 3285 5185 9806 2 -// 8 1174 1071 1095 1286 1640 2398 3869 6378 12080 2 -// 16 2279 1874 1745 1739 2144 2831 4209 6964 12007 4 -// 32 3910 3706 2588 2477 2782 3467 4856 7489 12618 4 -// 64 7184 5429 4710 4368 4010 4672 6027 8559 13684 5 -// 128 14097 10574 8452 7297 6841 6718 8615 10580 15641 6 -// 256 27715 20800 16000 13550 11875 11400 11505 14090 18460 6 -// 512 55100 41250 31740 26570 22030 19830 20760 21380 25215 6 -// 1024 111520 79000 61080 49720 43080 38320 37600 35040 36750 8 -// 2048 219480 162680 122120 102080 83760 70360 66600 63920 66160 8 -// 4096 453320 323080 247240 210200 180040 150240 132440 114920 110560 9 - -// 2 4 8 16 32 64 128 256 512 1024 2048 4096 -// Bos Coster 858 994 1316 1949 3183 5512 9865 17830 33485 63160 124280 246320 -// Straus 226 341 548 980 1870 3538 7039 14490 29020 57200 118640 233640 -// Straus/cached 226 315 485 785 1514 2858 5753 11065 22970 45120 98880 194840 -// Pippenger 555 747 1071 1739 2477 4010 6718 11400 19830 35040 63920 110560 - -// Best/cached Straus Straus Straus Straus Straus Straus Straus Straus Pip Pip Pip Pip -// Best/uncached Straus Straus Straus Straus Straus Straus Pip Pip Pip Pip Pip Pip +// 1 2 3 4 5 6 7 8 9 bestN +// 2 555 598 621 804 1038 1733 2486 5020 8304 1 +// 4 783 747 800 1006 1428 2132 3285 5185 9806 2 +// 8 1174 1071 1095 1286 1640 2398 3869 6378 12080 2 +// 16 2279 1874 1745 1739 2144 2831 4209 6964 12007 4 +// 32 3910 3706 2588 2477 2782 3467 4856 7489 12618 4 +// 64 7184 5429 4710 4368 4010 4672 6027 8559 13684 5 +// 128 14097 10574 8452 7297 6841 6718 8615 10580 15641 6 +// 256 27715 20800 16000 13550 11875 11400 11505 14090 18460 6 +// 512 55100 41250 31740 26570 22030 19830 20760 21380 25215 6 +// 1024 111520 79000 61080 49720 43080 38320 37600 35040 36750 8 +// 2048 219480 162680 122120 102080 83760 70360 66600 63920 66160 8 +// 4096 453320 323080 247240 210200 180040 150240 132440 114920 110560 9 + +// 2 4 8 16 32 64 128 256 512 1024 2048 4096 +// Bos Coster 858 994 1316 1949 3183 5512 9865 17830 33485 63160 124280 246320 +// Straus 226 341 548 980 1870 3538 7039 14490 29020 57200 118640 233640 +// Straus/cached 226 315 485 785 1514 2858 5753 11065 22970 45120 98880 194840 +// Pippenger 555 747 1071 1739 2477 4010 6718 11400 19830 35040 63920 110560 + +// Best/cached Straus Straus Straus Straus Straus Straus Straus Straus Pip Pip Pip Pip +// Best/uncached Straus Straus Straus Straus Straus Straus Pip Pip Pip Pip Pip Pip // New timings: // Pippenger: @@ -443,7 +443,7 @@ size_t straus_get_cache_size(const std::shared_ptr<straus_cached_data> &cache) return sz; } -rct::key straus(const std::vector<MultiexpData> &data, const std::shared_ptr<straus_cached_data> &cache, size_t STEP) +ge_p3 straus_p3(const std::vector<MultiexpData> &data, const std::shared_ptr<straus_cached_data> &cache, size_t STEP) { CHECK_AND_ASSERT_THROW_MES(cache == NULL || cache->size >= data.size(), "Cache is too small"); MULTIEXP_PERF(PERF_TIMER_UNIT(straus, 1000000)); @@ -554,7 +554,13 @@ skipfirst: ge_p1p1_to_p3(&res_p3, &p1); } + return res_p3; +} + +rct::key straus(const std::vector<MultiexpData> &data, const std::shared_ptr<straus_cached_data> &cache, size_t STEP) +{ rct::key res; + const ge_p3 res_p3 = straus_p3(data, cache, STEP); ge_p3_tobytes(res.bytes, &res_p3); return res; } @@ -571,14 +577,6 @@ size_t get_pippenger_c(size_t N) return 9; } -struct pippenger_cached_data -{ - size_t size; - ge_cached *cached; - pippenger_cached_data(): size(0), cached(NULL) {} - ~pippenger_cached_data() { aligned_free(cached); } -}; - std::shared_ptr<pippenger_cached_data> pippenger_init_cache(const std::vector<MultiexpData> &data, size_t start_offset, size_t N) { MULTIEXP_PERF(PERF_TIMER_START_UNIT(pippenger_init_cache, 1000000)); @@ -586,13 +584,11 @@ std::shared_ptr<pippenger_cached_data> pippenger_init_cache(const std::vector<Mu if (N == 0) N = data.size() - start_offset; CHECK_AND_ASSERT_THROW_MES(N <= data.size() - start_offset, "Bad cache base data"); - std::shared_ptr<pippenger_cached_data> cache(new pippenger_cached_data()); + std::shared_ptr<pippenger_cached_data> cache = std::make_shared<pippenger_cached_data>(); - cache->size = N; - cache->cached = (ge_cached*)aligned_realloc(cache->cached, N * sizeof(ge_cached), 4096); - CHECK_AND_ASSERT_THROW_MES(cache->cached, "Out of memory"); + cache->resize(N); for (size_t i = 0; i < N; ++i) - ge_p3_to_cached(&cache->cached[i], &data[i+start_offset].point); + ge_p3_to_cached(&(*cache)[i], &data[i+start_offset].point); MULTIEXP_PERF(PERF_TIMER_STOP(pippenger_init_cache)); return cache; @@ -600,14 +596,14 @@ std::shared_ptr<pippenger_cached_data> pippenger_init_cache(const std::vector<Mu size_t pippenger_get_cache_size(const std::shared_ptr<pippenger_cached_data> &cache) { - return cache->size * sizeof(*cache->cached); + return cache->size() * sizeof(ge_cached); } -rct::key pippenger(const std::vector<MultiexpData> &data, const std::shared_ptr<pippenger_cached_data> &cache, size_t cache_size, size_t c) +ge_p3 pippenger_p3(const std::vector<MultiexpData> &data, const std::shared_ptr<pippenger_cached_data> &cache, size_t cache_size, size_t c) { if (cache != NULL && cache_size == 0) - cache_size = cache->size; - CHECK_AND_ASSERT_THROW_MES(cache == NULL || cache_size <= cache->size, "Cache is too small"); + cache_size = cache->size(); + CHECK_AND_ASSERT_THROW_MES(cache == NULL || cache_size <= cache->size(), "Cache is too small"); if (c == 0) c = get_pippenger_c(data.size()); CHECK_AND_ASSERT_THROW_MES(c <= 9, "c is too large"); @@ -661,9 +657,9 @@ rct::key pippenger(const std::vector<MultiexpData> &data, const std::shared_ptr< if (buckets_init[bucket]) { if (i < cache_size) - add(buckets[bucket], local_cache->cached[i]); + add(buckets[bucket], (*local_cache)[i]); else - add(buckets[bucket], local_cache_2->cached[i - cache_size]); + add(buckets[bucket], (*local_cache_2)[i - cache_size]); } else { @@ -700,8 +696,14 @@ rct::key pippenger(const std::vector<MultiexpData> &data, const std::shared_ptr< } } + return result; +} + +rct::key pippenger(const std::vector<MultiexpData> &data, const std::shared_ptr<pippenger_cached_data> &cache, const size_t cache_size, const size_t c) +{ rct::key res; - ge_p3_tobytes(res.bytes, &result); + const ge_p3 result_p3 = pippenger_p3(data, cache, cache_size, c); + ge_p3_tobytes(res.bytes, &result_p3); return res; } diff --git a/src/ringct/multiexp.h b/src/ringct/multiexp.h index 788516c0b..ae10fcc8e 100644 --- a/src/ringct/multiexp.h +++ b/src/ringct/multiexp.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. @@ -35,10 +35,16 @@ #define MULTIEXP_H #include <vector> +extern "C" +{ +#include "crypto/crypto-ops.h" +} #include "crypto/crypto.h" #include "rctTypes.h" #include "misc_log_ex.h" +#include <boost/align/aligned_allocator.hpp> + namespace rct { @@ -55,17 +61,19 @@ struct MultiexpData { }; struct straus_cached_data; -struct pippenger_cached_data; +using pippenger_cached_data = std::vector<ge_cached, boost::alignment::aligned_allocator<ge_cached, 4096>>; rct::key bos_coster_heap_conv(std::vector<MultiexpData> data); rct::key bos_coster_heap_conv_robust(std::vector<MultiexpData> data); std::shared_ptr<straus_cached_data> straus_init_cache(const std::vector<MultiexpData> &data, size_t N =0); size_t straus_get_cache_size(const std::shared_ptr<straus_cached_data> &cache); +ge_p3 straus_p3(const std::vector<MultiexpData> &data, const std::shared_ptr<straus_cached_data> &cache = NULL, size_t STEP = 0); rct::key straus(const std::vector<MultiexpData> &data, const std::shared_ptr<straus_cached_data> &cache = NULL, size_t STEP = 0); std::shared_ptr<pippenger_cached_data> pippenger_init_cache(const std::vector<MultiexpData> &data, size_t start_offset = 0, size_t N =0); size_t pippenger_get_cache_size(const std::shared_ptr<pippenger_cached_data> &cache); size_t get_pippenger_c(size_t N); -rct::key pippenger(const std::vector<MultiexpData> &data, const std::shared_ptr<pippenger_cached_data> &cache = NULL, size_t cache_size = 0, size_t c = 0); +ge_p3 pippenger_p3(const std::vector<MultiexpData> &data, const std::shared_ptr<pippenger_cached_data> &cache = NULL, size_t cache_size = 0, size_t c = 0); +rct::key pippenger(const std::vector<MultiexpData> &data, const std::shared_ptr<pippenger_cached_data> &cache = NULL, const size_t cache_size = 0, const size_t c = 0); } diff --git a/src/ringct/rctCryptoOps.c b/src/ringct/rctCryptoOps.c index a2bb68665..b4fcc64bd 100644 --- a/src/ringct/rctCryptoOps.c +++ b/src/ringct/rctCryptoOps.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/ringct/rctCryptoOps.h b/src/ringct/rctCryptoOps.h index 2d6e13bb7..ec5708818 100644 --- a/src/ringct/rctCryptoOps.h +++ b/src/ringct/rctCryptoOps.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp index 245a3f477..56dd53b2c 100644 --- a/src/ringct/rctOps.cpp +++ b/src/ringct/rctOps.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016, Monero Research Labs +// Copyright (c) 2016-2023, Monero Research Labs // // Author: Shen Noether <shen.noether@gmx.com> // @@ -671,7 +671,7 @@ namespace rct { //Elliptic Curve Diffie Helman: encodes and decodes the amount b and mask a // where C= aG + bH - static key ecdhHash(const key &k) + key genAmountEncodingFactor(const key &k) { char data[38]; rct::key hash; @@ -700,7 +700,7 @@ namespace rct { if (v2) { unmasked.mask = zero(); - xor8(unmasked.amount, ecdhHash(sharedSec)); + xor8(unmasked.amount, genAmountEncodingFactor(sharedSec)); } else { @@ -715,7 +715,7 @@ namespace rct { if (v2) { masked.mask = genCommitmentMask(sharedSec); - xor8(masked.amount, ecdhHash(sharedSec)); + xor8(masked.amount, genAmountEncodingFactor(sharedSec)); } else { diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h index 679ed1441..eaab998cb 100644 --- a/src/ringct/rctOps.h +++ b/src/ringct/rctOps.h @@ -1,5 +1,5 @@ //#define DBG -// Copyright (c) 2016, Monero Research Labs +// Copyright (c) 2016-2023, Monero Research Labs // // Author: Shen Noether <shen.noether@gmx.com> // @@ -184,6 +184,7 @@ namespace rct { //Elliptic Curve Diffie Helman: encodes and decodes the amount b and mask a // where C= aG + bH + key genAmountEncodingFactor(const key &k); key genCommitmentMask(const key &sk); void ecdhEncode(ecdhTuple & unmasked, const key & sharedSec, bool v2); void ecdhDecode(ecdhTuple & masked, const key & sharedSec, bool v2); diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 21040317c..717bc27a3 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016, Monero Research Labs +// Copyright (c) 2016-2023, Monero Research Labs // // Author: Shen Noether <shen.noether@gmx.com> // @@ -1333,7 +1333,7 @@ namespace rct { try { if (semantics) { - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); std::deque<bool> results(rv.outPk.size(), false); DP("range proofs verified?"); @@ -1383,7 +1383,7 @@ namespace rct { { PERF_TIMER(verRctSemanticsSimple); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); std::deque<bool> results; std::vector<const Bulletproof*> bp_proofs; @@ -1536,7 +1536,7 @@ namespace rct { const size_t threads = std::max(rv.outPk.size(), rv.mixRing.size()); std::deque<bool> results(threads); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts; diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index 17cfd77b9..5c4773eb9 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016, Monero Research Labs +// Copyright (c) 2016-2023, Monero Research Labs // // Author: Shen Noether <shen.noether@gmx.com> // diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp index c22b0524f..4ac75b78b 100644 --- a/src/ringct/rctTypes.cpp +++ b/src/ringct/rctTypes.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016, Monero Research Labs +// Copyright (c) 2016-2023, Monero Research Labs // // Author: Shen Noether <shen.noether@gmx.com> // diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 59ed4d6a6..380ca2f53 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016, Monero Research Labs +// Copyright (c) 2016-2023, Monero Research Labs // // Author: Shen Noether <shen.noether@gmx.com> // @@ -97,6 +97,14 @@ namespace rct { struct ctkey { key dest; key mask; //C here if public + + bool operator==(const ctkey &other) const { + return (dest == other.dest) && (mask == other.mask); + } + + bool operator!=(const ctkey &other) const { + return !(*this == other); + } }; typedef std::vector<ctkey> ctkeyV; typedef std::vector<ctkeyV> ctkeyM; diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index edfc70067..fe80d3083 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/rpc/bootstrap_node_selector.cpp b/src/rpc/bootstrap_node_selector.cpp index a1cad3e59..d224c5c53 100644 --- a/src/rpc/bootstrap_node_selector.cpp +++ b/src/rpc/bootstrap_node_selector.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/rpc/bootstrap_node_selector.h b/src/rpc/bootstrap_node_selector.h index 616c180fc..3e52c3538 100644 --- a/src/rpc/bootstrap_node_selector.h +++ b/src/rpc/bootstrap_node_selector.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 5304333ff..cb347110d 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -598,88 +598,165 @@ namespace cryptonote CHECK_PAYMENT(req, res, 1); - // quick check for noop - if (!req.block_ids.empty()) + res.daemon_time = (uint64_t)time(NULL); + // Always set daemon time, and set it early rather than late, as delivering some incremental pool + // info twice because of slightly overlapping time intervals is no problem, whereas producing gaps + // and never delivering something is + + bool get_blocks = false; + bool get_pool = false; + switch (req.requested_info) { - uint64_t last_block_height; - crypto::hash last_block_hash; - m_core.get_blockchain_top(last_block_height, last_block_hash); - if (last_block_hash == req.block_ids.front()) - { - res.start_height = 0; - res.current_height = m_core.get_current_blockchain_height(); - res.status = CORE_RPC_STATUS_OK; + case COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY: + // Compatibility value 0: Clients that do not set 'requested_info' want blocks, and only blocks + get_blocks = true; + break; + case COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL: + get_blocks = true; + get_pool = true; + break; + case COMMAND_RPC_GET_BLOCKS_FAST::POOL_ONLY: + get_pool = true; + break; + default: + res.status = "Failed, wrong requested info"; return true; - } } - size_t max_blocks = COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT; - if (m_rpc_payment) + res.pool_info_extent = COMMAND_RPC_GET_BLOCKS_FAST::NONE; + + if (get_pool) { - max_blocks = res.credits / COST_PER_BLOCK; - if (max_blocks > COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT) - max_blocks = COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT; - if (max_blocks == 0) + const bool restricted = m_restricted && ctx; + const bool request_has_rpc_origin = ctx != NULL; + const bool allow_sensitive = !request_has_rpc_origin || !restricted; + const size_t max_tx_count = restricted ? RESTRICTED_TRANSACTIONS_COUNT : std::numeric_limits<size_t>::max(); + + bool incremental; + std::vector<std::pair<crypto::hash, tx_memory_pool::tx_details>> added_pool_txs; + bool success = m_core.get_pool_info((time_t)req.pool_info_since, allow_sensitive, max_tx_count, added_pool_txs, res.remaining_added_pool_txids, res.removed_pool_txids, incremental); + if (success) + { + res.added_pool_txs.clear(); + if (m_rpc_payment) + { + CHECK_PAYMENT_SAME_TS(req, res, added_pool_txs.size() * COST_PER_TX + (res.remaining_added_pool_txids.size() + res.removed_pool_txids.size()) * COST_PER_POOL_HASH); + } + for (const auto &added_pool_tx: added_pool_txs) + { + COMMAND_RPC_GET_BLOCKS_FAST::pool_tx_info info; + info.tx_hash = added_pool_tx.first; + std::stringstream oss; + binary_archive<true> ar(oss); + bool r = req.prune + ? const_cast<cryptonote::transaction&>(added_pool_tx.second.tx).serialize_base(ar) + : ::serialization::serialize(ar, const_cast<cryptonote::transaction&>(added_pool_tx.second.tx)); + if (!r) + { + res.status = "Failed to serialize transaction"; + return true; + } + info.tx_blob = oss.str(); + info.double_spend_seen = added_pool_tx.second.double_spend_seen; + res.added_pool_txs.push_back(std::move(info)); + } + } + if (success) + { + res.pool_info_extent = incremental ? COMMAND_RPC_GET_BLOCKS_FAST::INCREMENTAL : COMMAND_RPC_GET_BLOCKS_FAST::FULL; + } + else { - res.status = CORE_RPC_STATUS_PAYMENT_REQUIRED; + res.status = "Failed to get pool info"; return true; } } - std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > > bs; - if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, !req.no_miner_tx, max_blocks, COMMAND_RPC_GET_BLOCKS_FAST_MAX_TX_COUNT)) + if (get_blocks) { - res.status = "Failed"; - add_host_fail(ctx); - return true; - } - - CHECK_PAYMENT_SAME_TS(req, res, bs.size() * COST_PER_BLOCK); - - size_t size = 0, ntxes = 0; - res.blocks.reserve(bs.size()); - res.output_indices.reserve(bs.size()); - for(auto& bd: bs) - { - res.blocks.resize(res.blocks.size()+1); - res.blocks.back().pruned = req.prune; - res.blocks.back().block = bd.first.first; - size += bd.first.first.size(); - res.output_indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices()); - ntxes += bd.second.size(); - res.output_indices.back().indices.reserve(1 + bd.second.size()); - if (req.no_miner_tx) - res.output_indices.back().indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::tx_output_indices()); - res.blocks.back().txs.reserve(bd.second.size()); - for (std::vector<std::pair<crypto::hash, cryptonote::blobdata>>::iterator i = bd.second.begin(); i != bd.second.end(); ++i) + // quick check for noop + if (!req.block_ids.empty()) { - res.blocks.back().txs.push_back({std::move(i->second), crypto::null_hash}); - i->second.clear(); - i->second.shrink_to_fit(); - size += res.blocks.back().txs.back().blob.size(); + uint64_t last_block_height; + crypto::hash last_block_hash; + m_core.get_blockchain_top(last_block_height, last_block_hash); + if (last_block_hash == req.block_ids.front()) + { + res.start_height = 0; + res.current_height = last_block_height + 1; + res.status = CORE_RPC_STATUS_OK; + return true; + } } - const size_t n_txes_to_lookup = bd.second.size() + (req.no_miner_tx ? 0 : 1); - if (n_txes_to_lookup > 0) + size_t max_blocks = COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT; + if (m_rpc_payment) { - std::vector<std::vector<uint64_t>> indices; - bool r = m_core.get_tx_outputs_gindexs(req.no_miner_tx ? bd.second.front().first : bd.first.second, n_txes_to_lookup, indices); - if (!r) + max_blocks = res.credits / COST_PER_BLOCK; + if (max_blocks > COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT) + max_blocks = COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT; + if (max_blocks == 0) { - res.status = "Failed"; + res.status = CORE_RPC_STATUS_PAYMENT_REQUIRED; return true; } - if (indices.size() != n_txes_to_lookup || res.output_indices.back().indices.size() != (req.no_miner_tx ? 1 : 0)) + } + + std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > > bs; + if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, !req.no_miner_tx, max_blocks, COMMAND_RPC_GET_BLOCKS_FAST_MAX_TX_COUNT)) + { + res.status = "Failed"; + add_host_fail(ctx); + return true; + } + + CHECK_PAYMENT_SAME_TS(req, res, bs.size() * COST_PER_BLOCK); + + size_t size = 0, ntxes = 0; + res.blocks.reserve(bs.size()); + res.output_indices.reserve(bs.size()); + for(auto& bd: bs) + { + res.blocks.resize(res.blocks.size()+1); + res.blocks.back().pruned = req.prune; + res.blocks.back().block = bd.first.first; + size += bd.first.first.size(); + res.output_indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices()); + ntxes += bd.second.size(); + res.output_indices.back().indices.reserve(1 + bd.second.size()); + if (req.no_miner_tx) + res.output_indices.back().indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::tx_output_indices()); + res.blocks.back().txs.reserve(bd.second.size()); + for (std::vector<std::pair<crypto::hash, cryptonote::blobdata>>::iterator i = bd.second.begin(); i != bd.second.end(); ++i) { - res.status = "Failed"; - return true; + res.blocks.back().txs.push_back({std::move(i->second), crypto::null_hash}); + i->second.clear(); + i->second.shrink_to_fit(); + size += res.blocks.back().txs.back().blob.size(); + } + + const size_t n_txes_to_lookup = bd.second.size() + (req.no_miner_tx ? 0 : 1); + if (n_txes_to_lookup > 0) + { + std::vector<std::vector<uint64_t>> indices; + bool r = m_core.get_tx_outputs_gindexs(req.no_miner_tx ? bd.second.front().first : bd.first.second, n_txes_to_lookup, indices); + if (!r) + { + res.status = "Failed"; + return true; + } + if (indices.size() != n_txes_to_lookup || res.output_indices.back().indices.size() != (req.no_miner_tx ? 1 : 0)) + { + res.status = "Failed"; + return true; + } + for (size_t i = 0; i < indices.size(); ++i) + res.output_indices.back().indices.push_back({std::move(indices[i])}); } - for (size_t i = 0; i < indices.size(); ++i) - res.output_indices.back().indices.push_back({std::move(indices[i])}); } + MDEBUG("on_get_blocks: " << bs.size() << " blocks, " << ntxes << " txes, size " << size); } - MDEBUG("on_get_blocks: " << bs.size() << " blocks, " << ntxes << " txes, size " << size); res.status = CORE_RPC_STATUS_OK; return true; } @@ -919,17 +996,16 @@ namespace cryptonote // try the pool for any missing txes size_t found_in_pool = 0; std::unordered_set<crypto::hash> pool_tx_hashes; - std::unordered_map<crypto::hash, tx_info> per_tx_pool_tx_info; + std::unordered_map<crypto::hash, tx_memory_pool::tx_details> per_tx_pool_tx_details; if (!missed_txs.empty()) { - std::vector<tx_info> pool_tx_info; - std::vector<spent_key_image_info> pool_key_image_info; - bool r = m_core.get_pool_transactions_and_spent_keys_info(pool_tx_info, pool_key_image_info, !request_has_rpc_origin || !restricted); + std::vector<std::pair<crypto::hash, tx_memory_pool::tx_details>> pool_txs; + bool r = m_core.get_pool_transactions_info(missed_txs, pool_txs, !request_has_rpc_origin || !restricted); if(r) { // sort to match original request std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>> sorted_txs; - std::vector<tx_info>::const_iterator i; + std::vector<std::pair<crypto::hash, tx_memory_pool::tx_details>>::const_iterator i; unsigned txs_processed = 0; for (const crypto::hash &h: vh) { @@ -949,36 +1025,23 @@ namespace cryptonote sorted_txs.push_back(std::move(txs[txs_processed])); ++txs_processed; } - else if ((i = std::find_if(pool_tx_info.begin(), pool_tx_info.end(), [h](const tx_info &txi) { return epee::string_tools::pod_to_hex(h) == txi.id_hash; })) != pool_tx_info.end()) + else if ((i = std::find_if(pool_txs.begin(), pool_txs.end(), [h](const std::pair<crypto::hash, tx_memory_pool::tx_details> &pt) { return h == pt.first; })) != pool_txs.end()) { - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(i->tx_blob, tx)) - { - res.status = "Failed to parse and validate tx from blob"; - return true; - } + const tx_memory_pool::tx_details &td = i->second; std::stringstream ss; binary_archive<true> ba(ss); - bool r = const_cast<cryptonote::transaction&>(tx).serialize_base(ba); + bool r = const_cast<cryptonote::transaction&>(td.tx).serialize_base(ba); if (!r) { res.status = "Failed to serialize transaction base"; return true; } const cryptonote::blobdata pruned = ss.str(); - const crypto::hash prunable_hash = tx.version == 1 ? crypto::null_hash : get_transaction_prunable_hash(tx); - sorted_txs.push_back(std::make_tuple(h, pruned, prunable_hash, std::string(i->tx_blob, pruned.size()))); + const crypto::hash prunable_hash = td.tx.version == 1 ? crypto::null_hash : get_transaction_prunable_hash(td.tx); + sorted_txs.push_back(std::make_tuple(h, pruned, prunable_hash, std::string(td.tx_blob, pruned.size()))); missed_txs.erase(std::find(missed_txs.begin(), missed_txs.end(), h)); pool_tx_hashes.insert(h); - const std::string hash_string = epee::string_tools::pod_to_hex(h); - for (const auto &ti: pool_tx_info) - { - if (ti.id_hash == hash_string) - { - per_tx_pool_tx_info.insert(std::make_pair(h, ti)); - break; - } - } + per_tx_pool_tx_details.insert(std::make_pair(h, td)); ++found_in_pool; } } @@ -1009,7 +1072,17 @@ namespace cryptonote CHECK_AND_ASSERT_MES(tx_hash == std::get<0>(tx), false, "mismatched tx hash"); e.tx_hash = *txhi++; e.prunable_hash = epee::string_tools::pod_to_hex(std::get<2>(tx)); - if (req.split || req.prune || std::get<3>(tx).empty()) + + // coinbase txes do not have signatures to prune, so they appear to be pruned if looking just at prunable data being empty + bool pruned = std::get<3>(tx).empty(); + if (pruned) + { + cryptonote::transaction t; + if (cryptonote::parse_and_validate_tx_base_from_blob(std::get<1>(tx), t) && is_coinbase(t)) + pruned = false; + } + + if (req.split || req.prune || pruned) { // use splitted form with pruned and prunable (filled only when prune=false and the daemon has it), leaving as_hex as empty e.pruned_as_hex = string_tools::buff_to_hex_nodelimer(std::get<1>(tx)); @@ -1074,8 +1147,8 @@ namespace cryptonote { e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max(); e.confirmations = 0; - auto it = per_tx_pool_tx_info.find(tx_hash); - if (it != per_tx_pool_tx_info.end()) + auto it = per_tx_pool_tx_details.find(tx_hash); + if (it != per_tx_pool_tx_details.end()) { e.double_spend_seen = it->second.double_spend_seen; e.relayed = it->second.relayed; @@ -1240,6 +1313,7 @@ namespace cryptonote { LOG_PRINT_L0("[on_send_raw_tx]: Failed to parse tx from hexbuff: " << req.tx_as_hex); res.status = "Failed"; + res.reason = "Hex decoding failed"; return true; } @@ -1275,6 +1349,8 @@ namespace cryptonote add_reason(reason, "fee too low"); if ((res.too_few_outputs = tvc.m_too_few_outputs)) add_reason(reason, "too few outputs"); + if ((res.tx_extra_too_big = tvc.m_tx_extra_too_big)) + add_reason(reason, "tx-extra too big"); const std::string punctuation = reason.empty() ? "" : ": "; if (tvc.m_verifivation_failed) { @@ -2290,6 +2366,12 @@ namespace cryptonote return m_bootstrap_daemon->handle_result(false, {}); } + if (bootstrap_daemon_height < m_core.get_checkpoints().get_max_height()) + { + MINFO("Bootstrap daemon height is lower than the latest checkpoint"); + return m_bootstrap_daemon->handle_result(false, {}); + } + if (!m_p2p.get_payload_object().no_sync()) { uint64_t top_height = m_core.get_current_blockchain_height(); @@ -2855,6 +2937,10 @@ namespace cryptonote res.version = CORE_RPC_VERSION; res.release = MONERO_VERSION_IS_RELEASE; + res.current_height = m_core.get_current_blockchain_height(); + res.target_height = m_p2p.get_payload_object().is_synchronized() ? 0 : m_core.get_target_blockchain_height(); + for (const auto &hf : m_core.get_blockchain_storage().get_hardforks()) + res.hard_forks.push_back({hf.version, hf.height}); res.status = CORE_RPC_STATUS_OK; return true; } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 0274f4db8..790d5eb23 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -47,10 +47,6 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc" -// yes, epee doesn't properly use its full namespace when calling its -// functions from macros. *sigh* -using namespace epee; - namespace cryptonote { /************************************************************************/ diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1be2610ff..7f1581d0c 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -88,7 +88,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 3 -#define CORE_RPC_VERSION_MINOR 10 +#define CORE_RPC_VERSION_MINOR 12 #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) @@ -162,18 +162,29 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCKS_FAST { + enum REQUESTED_INFO + { + BLOCKS_ONLY = 0, + BLOCKS_AND_POOL = 1, + POOL_ONLY = 2 + }; + struct request_t: public rpc_access_request_base { + uint8_t requested_info; std::list<crypto::hash> block_ids; //*first 10 blocks id goes sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block */ uint64_t start_height; bool prune; bool no_miner_tx; + uint64_t pool_info_since; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_request_base) + KV_SERIALIZE_OPT(requested_info, (uint8_t)0) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) KV_SERIALIZE(start_height) KV_SERIALIZE(prune) KV_SERIALIZE_OPT(no_miner_tx, false) + KV_SERIALIZE_OPT(pool_info_since, (uint64_t)0) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -196,12 +207,37 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; + struct pool_tx_info + { + crypto::hash tx_hash; + blobdata tx_blob; + bool double_spend_seen; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_VAL_POD_AS_BLOB(tx_hash) + KV_SERIALIZE(tx_blob) + KV_SERIALIZE(double_spend_seen) + END_KV_SERIALIZE_MAP() + }; + + enum POOL_INFO_EXTENT + { + NONE = 0, + INCREMENTAL = 1, + FULL = 2 + }; + struct response_t: public rpc_access_response_base { std::vector<block_complete_entry> blocks; uint64_t start_height; uint64_t current_height; std::vector<block_output_indices> output_indices; + uint64_t daemon_time; + uint8_t pool_info_extent; + std::vector<pool_tx_info> added_pool_txs; + std::vector<crypto::hash> remaining_added_pool_txids; + std::vector<crypto::hash> removed_pool_txids; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_response_base) @@ -209,6 +245,17 @@ namespace cryptonote KV_SERIALIZE(start_height) KV_SERIALIZE(current_height) KV_SERIALIZE(output_indices) + KV_SERIALIZE_OPT(daemon_time, (uint64_t) 0) + KV_SERIALIZE_OPT(pool_info_extent, (uint8_t) 0) + if (pool_info_extent != POOL_INFO_EXTENT::NONE) + { + KV_SERIALIZE(added_pool_txs) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(remaining_added_pool_txids) + } + if (pool_info_extent == POOL_INFO_EXTENT::INCREMENTAL) + { + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(removed_pool_txids) + } END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -592,6 +639,7 @@ namespace cryptonote bool fee_too_low; bool too_few_outputs; bool sanity_check_failed; + bool tx_extra_too_big; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_response_base) @@ -606,6 +654,7 @@ namespace cryptonote KV_SERIALIZE(fee_too_low) KV_SERIALIZE(too_few_outputs) KV_SERIALIZE(sanity_check_failed) + KV_SERIALIZE(tx_extra_too_big) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2123,15 +2172,34 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; + struct hf_entry + { + uint8_t hf_version; + uint64_t height; + + bool operator==(const hf_entry& hfe) const { return hf_version == hfe.hf_version && height == hfe.height; } + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(hf_version) + KV_SERIALIZE(height) + END_KV_SERIALIZE_MAP() + }; + struct response_t: public rpc_response_base { uint32_t version; bool release; + uint64_t current_height; + uint64_t target_height; + std::vector<hf_entry> hard_forks; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(version) KV_SERIALIZE(release) + KV_SERIALIZE_OPT(current_height, (uint64_t)0) + KV_SERIALIZE_OPT(target_height, (uint64_t)0) + KV_SERIALIZE_OPT(hard_forks, std::vector<hf_entry>()) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/rpc/core_rpc_server_error_codes.h b/src/rpc/core_rpc_server_error_codes.h index 1ba2392a9..2a4726a89 100644 --- a/src/rpc/core_rpc_server_error_codes.h +++ b/src/rpc/core_rpc_server_error_codes.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index 0466c92c2..52067bd4d 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/daemon_handler.h b/src/rpc/daemon_handler.h index 74885cf30..11a6c2849 100644 --- a/src/rpc/daemon_handler.h +++ b/src/rpc/daemon_handler.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/daemon_messages.cpp b/src/rpc/daemon_messages.cpp index a7c921ff9..0da22f15f 100644 --- a/src/rpc/daemon_messages.cpp +++ b/src/rpc/daemon_messages.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/daemon_messages.h b/src/rpc/daemon_messages.h index fb33f0d4a..d4b55f8fd 100644 --- a/src/rpc/daemon_messages.h +++ b/src/rpc/daemon_messages.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/daemon_rpc_version.h b/src/rpc/daemon_rpc_version.h index 62847fd58..d14b85ed9 100644 --- a/src/rpc/daemon_rpc_version.h +++ b/src/rpc/daemon_rpc_version.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/fwd.h b/src/rpc/fwd.h index cd9eacb9a..6227f5696 100644 --- a/src/rpc/fwd.h +++ b/src/rpc/fwd.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/instanciations.cpp b/src/rpc/instanciations.cpp index 6e48d36a8..d4f5231f3 100644 --- a/src/rpc/instanciations.cpp +++ b/src/rpc/instanciations.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/message.cpp b/src/rpc/message.cpp index 005c40ea2..9fda96e71 100644 --- a/src/rpc/message.cpp +++ b/src/rpc/message.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/message.h b/src/rpc/message.h index 730b3ec74..b7253e09b 100644 --- a/src/rpc/message.h +++ b/src/rpc/message.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h index dd9d198ed..542d71239 100644 --- a/src/rpc/message_data_structs.h +++ b/src/rpc/message_data_structs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp index 74f0879af..e6233fa98 100644 --- a/src/rpc/rpc_args.cpp +++ b/src/rpc/rpc_args.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_args.h b/src/rpc/rpc_args.h index 0b0dac57a..ac6a5d7cd 100644 --- a/src/rpc/rpc_args.h +++ b/src/rpc/rpc_args.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_handler.h b/src/rpc/rpc_handler.h index c84460549..e13d3036f 100644 --- a/src/rpc/rpc_handler.h +++ b/src/rpc/rpc_handler.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_payment.cpp b/src/rpc/rpc_payment.cpp index 2e05f04fb..71a00aca4 100644 --- a/src/rpc/rpc_payment.cpp +++ b/src/rpc/rpc_payment.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. // @@ -236,10 +236,8 @@ namespace cryptonote *(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(nonce); if (block.major_version >= RX_BLOCK_VERSION) { - const uint64_t seed_height = is_current ? info.seed_height : info.previous_seed_height; const crypto::hash &seed_hash = is_current ? info.seed_hash : info.previous_seed_hash; - const uint64_t height = cryptonote::get_block_height(block); - crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data, 0, 0); + crypto::rx_slow_hash(seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data); } else { diff --git a/src/rpc/rpc_payment.h b/src/rpc/rpc_payment.h index e93f27d24..4ead5a344 100644 --- a/src/rpc/rpc_payment.h +++ b/src/rpc/rpc_payment.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_payment_costs.h b/src/rpc/rpc_payment_costs.h index 6ba4e8064..36ee5ec1d 100644 --- a/src/rpc/rpc_payment_costs.h +++ b/src/rpc/rpc_payment_costs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_payment_signature.cpp b/src/rpc/rpc_payment_signature.cpp index 4e4798aae..ec7aeb715 100644 --- a/src/rpc/rpc_payment_signature.cpp +++ b/src/rpc/rpc_payment_signature.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_payment_signature.h b/src/rpc/rpc_payment_signature.h index 55ba49e9a..f0539446f 100644 --- a/src/rpc/rpc_payment_signature.h +++ b/src/rpc/rpc_payment_signature.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_version_str.cpp b/src/rpc/rpc_version_str.cpp index 528c92415..07756d636 100644 --- a/src/rpc/rpc_version_str.cpp +++ b/src/rpc/rpc_version_str.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/rpc_version_str.h b/src/rpc/rpc_version_str.h index b81a76980..2cfa9f867 100644 --- a/src/rpc/rpc_version_str.h +++ b/src/rpc/rpc_version_str.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/zmq_pub.cpp b/src/rpc/zmq_pub.cpp index 81e6de99a..4c620c59a 100644 --- a/src/rpc/zmq_pub.cpp +++ b/src/rpc/zmq_pub.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/rpc/zmq_pub.h b/src/rpc/zmq_pub.h index 9554f26be..fe60527a6 100644 --- a/src/rpc/zmq_pub.h +++ b/src/rpc/zmq_pub.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022, The Monero Project +// Copyright (c) 2020-2023, The Monero Project // // All rights reserved. diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp index 398a0499a..cb8a8bea4 100644 --- a/src/rpc/zmq_server.cpp +++ b/src/rpc/zmq_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/rpc/zmq_server.h b/src/rpc/zmq_server.h index 2d4049baa..b561fd580 100644 --- a/src/rpc/zmq_server.h +++ b/src/rpc/zmq_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/CMakeLists.txt b/src/serialization/CMakeLists.txt index d3b9e57ca..07076bae2 100644 --- a/src/serialization/CMakeLists.txt +++ b/src/serialization/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2022, The Monero Project +# Copyright (c) 2016-2023, The Monero Project # # All rights reserved. # diff --git a/src/serialization/binary_archive.h b/src/serialization/binary_archive.h index 07a4ec169..b3946a84b 100644 --- a/src/serialization/binary_archive.h +++ b/src/serialization/binary_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/binary_utils.h b/src/serialization/binary_utils.h index 9b41e9529..247c60beb 100644 --- a/src/serialization/binary_utils.h +++ b/src/serialization/binary_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/container.h b/src/serialization/container.h index 7b59e9408..80e1892f2 100644 --- a/src/serialization/container.h +++ b/src/serialization/container.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/containers.h b/src/serialization/containers.h index dd2de829a..65e6359b2 100644 --- a/src/serialization/containers.h +++ b/src/serialization/containers.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/crypto.h b/src/serialization/crypto.h index c2a4846ee..896d00583 100644 --- a/src/serialization/crypto.h +++ b/src/serialization/crypto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/debug_archive.h b/src/serialization/debug_archive.h index 479ed0cac..ef3bb4345 100644 --- a/src/serialization/debug_archive.h +++ b/src/serialization/debug_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/difficulty_type.h b/src/serialization/difficulty_type.h index b13693c26..37761839b 100644 --- a/src/serialization/difficulty_type.h +++ b/src/serialization/difficulty_type.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022, The Monero Project +// Copyright (c) 2019-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/json_archive.h b/src/serialization/json_archive.h index 8c4486d05..194ddaee9 100644 --- a/src/serialization/json_archive.h +++ b/src/serialization/json_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index 8de5860f6..4e436c254 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h index 968707165..513ecfd34 100644 --- a/src/serialization/json_object.h +++ b/src/serialization/json_object.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2022, The Monero Project +// Copyright (c) 2016-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/pair.h b/src/serialization/pair.h index f18260dc8..f21041fe8 100644 --- a/src/serialization/pair.h +++ b/src/serialization/pair.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index 381d29cfc..efe1270a4 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/string.h b/src/serialization/string.h index 976924602..a5d68eb64 100644 --- a/src/serialization/string.h +++ b/src/serialization/string.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/serialization/tuple.h b/src/serialization/tuple.h new file mode 100644 index 000000000..e76ca0b99 --- /dev/null +++ b/src/serialization/tuple.h @@ -0,0 +1,169 @@ +// Copyright (c) 2014-2023, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include <memory> +#include "serialization.h" + +namespace serialization +{ + namespace detail + { + template <typename Archive, class T> + bool serialize_tuple_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template <typename Archive> + bool serialize_tuple_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + } +} + +template <template <bool> class Archive, class E0, class E1, class E2> +inline bool do_serialize(Archive<false>& ar, std::tuple<E0,E1,E2>& p) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.good()) + return false; + if (cnt != 3) + return false; + + if (!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) + return false; + if (!ar.good()) + return false; + + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class E0, class E1, class E2> +inline bool do_serialize(Archive<true>& ar, std::tuple<E0,E1,E2>& p) +{ + ar.begin_array(3); + if (!ar.good()) + return false; + if(!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) + return false; + if (!ar.good()) + return false; + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class E0, class E1, class E2, class E3> +inline bool do_serialize(Archive<false>& ar, std::tuple<E0,E1,E2,E3>& p) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.good()) + return false; + if (cnt != 4) + return false; + + if (!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if (!::serialization::detail::serialize_tuple_element(ar, std::get<3>(p))) + return false; + if (!ar.good()) + return false; + + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class E0, class E1, class E2, class E3> +inline bool do_serialize(Archive<true>& ar, std::tuple<E0,E1,E2,E3>& p) +{ + ar.begin_array(4); + if (!ar.good()) + return false; + if(!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) + return false; + if (!ar.good()) + return false; + ar.delimit_array(); + if(!::serialization::detail::serialize_tuple_element(ar, std::get<3>(p))) + return false; + if (!ar.good()) + return false; + ar.end_array(); + return true; +} + diff --git a/src/serialization/variant.h b/src/serialization/variant.h index 2b3c75ce5..77d767a1c 100644 --- a/src/serialization/variant.h +++ b/src/serialization/variant.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt index b659d9f48..9c1d2a339 100644 --- a/src/simplewallet/CMakeLists.txt +++ b/src/simplewallet/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index a8f4e5a07..e618bfa6f 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -64,7 +64,6 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "storages/http_abstract_invoke.h" #include "rpc/core_rpc_server_commands_defs.h" -#include "rpc/rpc_payment_signature.h" #include "crypto/crypto.h" // for crypto::secret_key definition #include "mnemonics/electrum-words.h" #include "rapidjson/document.h" @@ -105,15 +104,12 @@ typedef cryptonote::simple_wallet sw; bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \ /* stop any background refresh and other processes, and take over */ \ - m_suspend_rpc_payment_mining.store(true, std::memory_order_relaxed); \ m_wallet->stop(); \ boost::unique_lock<boost::mutex> lock(m_idle_mutex); \ m_idle_cond.notify_all(); \ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ /* m_idle_mutex is still locked here */ \ m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ - m_suspend_rpc_payment_mining.store(false, std::memory_order_relaxed); \ - m_rpc_payment_checker.trigger(); \ m_idle_cond.notify_one(); \ }) @@ -137,9 +133,6 @@ typedef cryptonote::simple_wallet sw; #define REFRESH_PERIOD 90 // seconds -#define CREDITS_TARGET 50000 -#define MAX_PAYMENT_DIFF 10000 -#define MIN_PAYMENT_RATE 0.01f // per hash #define MAX_MNEW_ADDRESSES 1000 #define CHECK_MULTISIG_ENABLED() \ @@ -167,7 +160,6 @@ namespace { const std::array<const char* const, 5> allowed_priority_strings = {{"default", "unimportant", "normal", "elevated", "priority"}}; const auto arg_wallet_file = wallet_args::arg_wallet_file(); - const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key(); const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to <arg>"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_device = {"generate-from-device", sw::tr("Generate new wallet from device and save it to <arg>"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""}; @@ -181,7 +173,6 @@ namespace const command_line::arg_descriptor<bool> arg_restore_from_seed = {"restore-from-seed", sw::tr("alias for --restore-deterministic-wallet"), false}; const command_line::arg_descriptor<bool> arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Generate non-deterministic view and spend keys"), false}; - const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; const command_line::arg_descriptor<uint64_t> arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0}; const command_line::arg_descriptor<std::string> arg_restore_date = {"restore-date", sw::tr("Restore from estimated blockchain height on specified date"), ""}; const command_line::arg_descriptor<bool> arg_do_not_relay = {"do-not-relay", sw::tr("The newly created transaction will not be relayed to the monero network"), false}; @@ -244,7 +235,7 @@ namespace const char* USAGE_IMPORT_OUTPUTS("import_outputs <filename>"); const char* USAGE_SHOW_TRANSFER("show_transfer <txid>"); const char* USAGE_MAKE_MULTISIG("make_multisig <threshold> <string1> [<string>...]"); - const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys <string> [<string>...]"); + const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys [force-update-use-with-caution] <string> [<string>...]"); const char* USAGE_EXPORT_MULTISIG_INFO("export_multisig_info <filename>"); const char* USAGE_IMPORT_MULTISIG_INFO("import_multisig_info <filename> [<filename>...]"); const char* USAGE_SIGN_MULTISIG("sign_multisig <filename>"); @@ -284,9 +275,6 @@ namespace const char* USAGE_NET_STATS("net_stats"); const char* USAGE_PUBLIC_NODES("public_nodes"); const char* USAGE_WELCOME("welcome"); - const char* USAGE_RPC_PAYMENT_INFO("rpc_payment_info"); - const char* USAGE_START_MINING_FOR_RPC("start_mining_for_rpc [<number_of_threads>]"); - const char* USAGE_STOP_MINING_FOR_RPC("stop_mining_for_rpc"); const char* USAGE_SHOW_QR_CODE("show_qr_code [<subaddress_index>]"); const char* USAGE_VERSION("version"); const char* USAGE_HELP("help [<command> | all]"); @@ -541,10 +529,9 @@ void simple_wallet::handle_transfer_exception(const std::exception_ptr &e, bool { std::rethrow_exception(e); } - catch (const tools::error::payment_required&) + catch (const tools::error::deprecated_rpc_access&) { - fail_msg_writer() << tr("Payment required, see the 'rpc_payment_info' command"); - m_need_payment = true; + fail_msg_writer() << tr("Daemon requires deprecated RPC payment. See https://github.com/monero-project/monero/issues/8722"); } catch (const tools::error::no_connection_to_daemon&) { @@ -1139,11 +1126,22 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) { CHECK_MULTISIG_ENABLED(); - exchange_multisig_keys_main(args, false); + bool force_update_use_with_caution = false; + + auto local_args = args; + if (args.size() >= 1 && local_args[0] == "force-update-use-with-caution") + { + force_update_use_with_caution = true; + local_args.erase(local_args.begin()); + } + + exchange_multisig_keys_main(local_args, force_update_use_with_caution, false); return true; } -bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms) { +bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, + const bool force_update_use_with_caution, + const bool called_by_mms) { CHECK_MULTISIG_ENABLED(); bool ready; if (m_wallet->key_on_device()) @@ -1169,15 +1167,9 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & return false; } - if (args.size() < 1) - { - PRINT_USAGE(USAGE_EXCHANGE_MULTISIG_KEYS); - return false; - } - try { - std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args); + std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args, force_update_use_with_caution); bool ready; m_wallet->multisig(&ready); if (!ready) @@ -1920,77 +1912,6 @@ bool simple_wallet::unset_ring(const std::vector<std::string> &args) return true; } -bool simple_wallet::rpc_payment_info(const std::vector<std::string> &args) -{ - if (!try_connect_to_daemon()) - return true; - - LOCK_IDLE_SCOPE(); - - try - { - bool payment_required; - uint64_t credits, diff, credits_per_hash_found, height, seed_height; - uint32_t cookie; - std::string hashing_blob; - crypto::hash seed_hash, next_seed_hash; - crypto::public_key pkey; - crypto::secret_key_to_public_key(m_wallet->get_rpc_client_secret_key(), pkey); - message_writer() << tr("RPC client ID: ") << pkey; - message_writer() << tr("RPC client secret key: ") << m_wallet->get_rpc_client_secret_key(); - if (!m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie)) - { - fail_msg_writer() << tr("Failed to query daemon"); - return true; - } - if (payment_required) - { - uint64_t target = m_wallet->credits_target(); - if (target == 0) - target = CREDITS_TARGET; - message_writer() << tr("Using daemon: ") << m_wallet->get_daemon_address(); - message_writer() << tr("Payments required for node use, current credits: ") << credits; - message_writer() << tr("Credits target: ") << target; - uint64_t expected, discrepancy; - m_wallet->credit_report(expected, discrepancy); - message_writer() << tr("Credits spent this session: ") << expected; - if (expected) - message_writer() << tr("Credit discrepancy this session: ") << discrepancy << " (" << 100.0f * discrepancy / expected << "%)"; - float cph = credits_per_hash_found / (float)diff; - message_writer() << tr("Difficulty: ") << diff << ", " << credits_per_hash_found << " " << tr("credits per hash found, ") << cph << " " << tr("credits/hash"); - const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); - bool mining = (now - m_last_rpc_payment_mining_time).total_microseconds() < 1000000; - if (mining) - { - float hash_rate = m_rpc_payment_hash_rate; - if (hash_rate > 0) - { - message_writer() << (boost::format(tr("Mining for payment at %.1f H/s")) % hash_rate).str(); - if (credits < target) - { - std::chrono::seconds seconds((unsigned)((target - credits) / cph / hash_rate)); - std::string target_string = get_human_readable_timespan(seconds); - message_writer() << (boost::format(tr("Estimated time till %u credits target mined: %s")) % target % target_string).str(); - } - } - else - message_writer() << tr("Mining for payment"); - } - else - message_writer() << tr("Not mining"); - } - else - message_writer() << tr("No payment needed for node use"); - } - catch (const std::exception& e) - { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << tr("unexpected error: ") << e.what(); - } - - return true; -} - bool simple_wallet::blackball(const std::vector<std::string> &args) { uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets; @@ -2238,31 +2159,19 @@ bool simple_wallet::public_nodes(const std::vector<std::string> &args) try { auto nodes = m_wallet->get_public_nodes(false); - m_claimed_cph.clear(); if (nodes.empty()) { fail_msg_writer() << tr("No known public nodes"); return true; } - std::sort(nodes.begin(), nodes.end(), [](const public_node &node0, const public_node &node1) { - if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash == 0) - return true; - if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash) - return node0.rpc_credits_per_hash < node1.rpc_credits_per_hash; - return false; - }); const uint64_t now = time(NULL); - message_writer() << boost::format("%32s %12s %16s") % tr("address") % tr("credits/hash") % tr("last_seen"); + message_writer() << boost::format("%32s %16s") % tr("address") % tr("last_seen"); for (const auto &node: nodes) { - const float cph = node.rpc_credits_per_hash / RPC_CREDITS_PER_HASH_SCALE; - char cphs[9]; - snprintf(cphs, sizeof(cphs), "%.3f", cph); const std::string last_seen = node.last_seen == 0 ? tr("never") : get_human_readable_timespan(std::chrono::seconds(now - node.last_seen)); std::string host = node.host + ":" + std::to_string(node.rpc_port); - message_writer() << boost::format("%32s %12s %16s") % host % cphs % last_seen; - m_claimed_cph[host] = node.rpc_credits_per_hash; + message_writer() << boost::format("%32s %16s") % host % last_seen; } } catch (const std::exception &e) @@ -2337,68 +2246,6 @@ bool simple_wallet::cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& return m_wallet->import_key_images(exported_txs, 0, true); } -bool simple_wallet::start_mining_for_rpc(const std::vector<std::string> &args) -{ - if (!try_connect_to_daemon()) - return true; - - bool ok = true; - if(args.size() >= 1) - { - uint16_t num = 0; - ok = string_tools::get_xtype_from_string(num, args[0]); - m_rpc_payment_threads = num; - } - else - { - m_rpc_payment_threads = 0; - } - - if (!ok) - { - PRINT_USAGE(USAGE_START_MINING_FOR_RPC); - return true; - } - - LOCK_IDLE_SCOPE(); - - bool payment_required; - uint64_t credits, diff, credits_per_hash_found, height, seed_height; - uint32_t cookie; - std::string hashing_blob; - crypto::hash seed_hash, next_seed_hash; - if (!m_wallet->get_rpc_payment_info(true, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie)) - { - fail_msg_writer() << tr("Failed to query daemon"); - return true; - } - if (!payment_required) - { - fail_msg_writer() << tr("Daemon does not require payment for RPC access"); - return true; - } - - m_rpc_payment_mining_requested = true; - m_rpc_payment_checker.trigger(); - const float cph = credits_per_hash_found / (float)diff; - bool low = (diff > MAX_PAYMENT_DIFF || cph < MIN_PAYMENT_RATE); - success_msg_writer() << (boost::format(tr("Starting mining for RPC access: diff %llu, %f credits/hash%s")) % diff % cph % (low ? " - this is low" : "")).str(); - success_msg_writer() << tr("Run stop_mining_for_rpc to stop"); - return true; -} - -bool simple_wallet::stop_mining_for_rpc(const std::vector<std::string> &args) -{ - if (!try_connect_to_daemon()) - return true; - - LOCK_IDLE_SCOPE(); - m_rpc_payment_mining_requested = false; - m_last_rpc_payment_mining_time = boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1)); - m_rpc_payment_hash_rate = -1.0f; - return true; -} - bool simple_wallet::show_qr_code(const std::vector<std::string> &args) { uint32_t subaddress_index = 0; @@ -2808,53 +2655,6 @@ bool simple_wallet::set_segregate_pre_fork_outputs(const std::vector<std::string return true; } -bool simple_wallet::set_persistent_rpc_client_id(const std::vector<std::string> &args/* = std::vector<std::string>()*/) -{ - const auto pwd_container = get_and_verify_password(); - if (pwd_container) - { - parse_bool_and_use(args[1], [&](bool r) { - m_wallet->persistent_rpc_client_id(r); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); - }); - } - return true; -} - -bool simple_wallet::set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args/* = std::vector<std::string>()*/) -{ - const auto pwd_container = get_and_verify_password(); - if (pwd_container) - { - float threshold; - if (!epee::string_tools::get_xtype_from_string(threshold, args[1]) || threshold < 0.0f) - { - fail_msg_writer() << tr("Invalid threshold"); - return true; - } - m_wallet->auto_mine_for_rpc_payment_threshold(threshold); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); - } - return true; -} - -bool simple_wallet::set_credits_target(const std::vector<std::string> &args/* = std::vector<std::string>()*/) -{ - const auto pwd_container = get_and_verify_password(); - if (pwd_container) - { - uint64_t target; - if (!epee::string_tools::get_xtype_from_string(target, args[1])) - { - fail_msg_writer() << tr("Invalid target"); - return true; - } - m_wallet->credits_target(target); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); - } - return true; -} - bool simple_wallet::set_key_reuse_mitigation2(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -3211,7 +3011,6 @@ bool simple_wallet::scan_tx(const std::vector<std::string> &args) } txids.insert(txid); } - std::vector<crypto::hash> txids_v(txids.begin(), txids.end()); if (!m_wallet->is_trusted_daemon()) { message_writer(console_color_red, true) << tr("WARNING: this operation may reveal the txids to the remote node and affect your privacy"); @@ -3224,7 +3023,9 @@ bool simple_wallet::scan_tx(const std::vector<std::string> &args) LOCK_IDLE_SCOPE(); m_in_manual_refresh.store(true); try { - m_wallet->scan_tx(txids_v); + m_wallet->scan_tx(txids); + } catch (const tools::error::wont_reprocess_recent_txs_via_untrusted_daemon &e) { + fail_msg_writer() << e.what() << ". Either connect to a trusted daemon by passing --trusted-daemon when starting the wallet, or use rescan_bc to rescan the chain."; } catch (const std::exception &e) { fail_msg_writer() << e.what(); } @@ -3233,8 +3034,7 @@ bool simple_wallet::scan_tx(const std::vector<std::string> &args) } simple_wallet::simple_wallet() - : m_allow_mismatched_daemon_version(false) - , m_refresh_progress_reporter(*this) + : m_refresh_progress_reporter(*this) , m_idle_run(true) , m_auto_refresh_enabled(false) , m_auto_refresh_refreshing(false) @@ -3243,12 +3043,6 @@ simple_wallet::simple_wallet() , m_last_activity_time(time(NULL)) , m_locked(false) , m_in_command(false) - , m_need_payment(false) - , m_rpc_payment_mining_requested(false) - , m_last_rpc_payment_mining_time(boost::gregorian::date(1970, 1, 1)) - , m_daemon_rpc_payment_message_displayed(false) - , m_rpc_payment_hash_rate(-1.0f) - , m_suspend_rpc_payment_mining(false) { m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::on_command, this, &simple_wallet::start_mining, _1), @@ -3430,12 +3224,6 @@ simple_wallet::simple_wallet() " Device name for hardware wallet.\n " "export-format <\"binary\"|\"ascii\">\n " " Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n " - "persistent-rpc-client-id <1|0>\n " - " Whether to keep using the same client id for RPC payment over wallet restarts.\n" - "auto-mine-for-rpc-payment-threshold <float>\n " - " Whether to automatically start mining for RPC payment if the daemon requires it.\n" - "credits-target <unsigned int>\n" - " The RPC payment credits balance to target (0 for default).\n " "show-wallet-name-when-locked <1|0>\n " " Set this if you would like to display the wallet name when locked.\n " "enable-multisig-experimental <1|0>\n " @@ -3756,18 +3544,6 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::on_command, this, &simple_wallet::version, _1), tr(USAGE_VERSION), tr("Returns version information")); - m_cmd_binder.set_handler("rpc_payment_info", - boost::bind(&simple_wallet::on_command, this, &simple_wallet::rpc_payment_info, _1), - tr(USAGE_RPC_PAYMENT_INFO), - tr("Get info about RPC payments to current node")); - m_cmd_binder.set_handler("start_mining_for_rpc", - boost::bind(&simple_wallet::on_command, this, &simple_wallet::start_mining_for_rpc, _1), - tr(USAGE_START_MINING_FOR_RPC), - tr("Start mining to pay for RPC access")); - m_cmd_binder.set_handler("stop_mining_for_rpc", - boost::bind(&simple_wallet::on_command, this, &simple_wallet::stop_mining_for_rpc, _1), - tr(USAGE_STOP_MINING_FOR_RPC), - tr("Stop mining to pay for RPC access")); m_cmd_binder.set_handler("show_qr_code", boost::bind(&simple_wallet::on_command, this, &simple_wallet::show_qr_code, _1), tr(USAGE_SHOW_QR_CODE), @@ -3851,9 +3627,6 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) << " (disabled on Windows)" #endif ; - success_msg_writer() << "persistent-rpc-client-id = " << m_wallet->persistent_rpc_client_id(); - success_msg_writer() << "auto-mine-for-rpc-payment-threshold = " << m_wallet->auto_mine_for_rpc_payment_threshold(); - success_msg_writer() << "credits-target = " << m_wallet->credits_target(); success_msg_writer() << "load-deprecated-formats = " << m_wallet->load_deprecated_formats(); success_msg_writer() << "enable-multisig-experimental = " << m_wallet->is_multisig_enabled(); return true; @@ -3919,9 +3692,6 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>")); CHECK_SIMPLE_VARIABLE("export-format", set_export_format, tr("\"binary\" or \"ascii\"")); CHECK_SIMPLE_VARIABLE("load-deprecated-formats", set_load_deprecated_formats, tr("0 or 1")); - CHECK_SIMPLE_VARIABLE("persistent-rpc-client-id", set_persistent_rpc_client_id, tr("0 or 1")); - CHECK_SIMPLE_VARIABLE("auto-mine-for-rpc-payment-threshold", set_auto_mine_for_rpc_payment_threshold, tr("floating point >= 0")); - CHECK_SIMPLE_VARIABLE("credits-target", set_credits_target, tr("unsigned integer")); CHECK_SIMPLE_VARIABLE("enable-multisig-experimental", set_enable_multisig, tr("0 or 1")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); @@ -4019,6 +3789,7 @@ bool simple_wallet::ask_wallet_create_if_needed() bool ok = true; if (!m_restoring) { + message_writer() << tr("Looking for filename: ") << boost::filesystem::complete(wallet_path); message_writer() << tr("No wallet found with that name. Confirm creation of new wallet named: ") << wallet_path; confirm_creation = input_line("", true); if(std::cin.eof()) @@ -4118,6 +3889,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) epee::wipeable_string multisig_keys; epee::wipeable_string password; + epee::wipeable_string seed_pass; if (!handle_command_line(vm)) return false; @@ -4134,6 +3906,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if(!ask_wallet_create_if_needed()) return false; } + bool enable_multisig = false; + if (m_restore_multisig_wallet) { + fail_msg_writer() << tr("Multisig is disabled."); + fail_msg_writer() << tr("Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member."); + if (!command_line::is_yes(input_line("Do you want to continue restoring a multisig wallet?", true))) { + message_writer() << tr("You have canceled restoring a multisig wallet."); + return false; + } + enable_multisig = true; + } + if (!m_generate_new.empty() || m_restoring) { if (!m_subaddress_lookahead.empty() && !parse_subaddress_lookahead(m_subaddress_lookahead)) @@ -4213,19 +3996,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto pwd_container = password_prompter(tr("Enter seed offset passphrase, empty if none"), false); if (std::cin.eof() || !pwd_container) return false; - epee::wipeable_string seed_pass = pwd_container->password(); - if (!seed_pass.empty()) - { - if (m_restore_multisig_wallet) - { - crypto::secret_key key; - crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key); - sc_reduce32((unsigned char*)key.data); - multisig_keys = m_wallet->decrypt<epee::wipeable_string>(std::string(multisig_keys.data(), multisig_keys.size()), key, true); - } - else - m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); - } + seed_pass = pwd_container->password(); + if (!seed_pass.empty() && !m_restore_multisig_wallet) + m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); } if (!m_generate_from_view_key.empty()) { @@ -4568,7 +4341,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) m_wallet_file = m_generate_new; boost::optional<epee::wipeable_string> r; if (m_restore_multisig_wallet) - r = new_wallet(vm, multisig_keys, old_language); + r = new_wallet(vm, multisig_keys, seed_pass, old_language); else r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); @@ -4667,6 +4440,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } m_wallet->set_refresh_from_block_height(m_restore_height); } + if (enable_multisig) + m_wallet->enable_multisig(true); m_wallet->rewrite(m_wallet_file, password); } else @@ -4687,17 +4462,6 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } - if (!command_line::is_arg_defaulted(vm, arg_rpc_client_secret_key)) - { - crypto::secret_key rpc_client_secret_key; - if (!epee::string_tools::hex_to_pod(command_line::get_arg(vm, arg_rpc_client_secret_key), rpc_client_secret_key)) - { - fail_msg_writer() << tr("RPC client secret key should be 32 byte in hex format"); - return false; - } - m_wallet->set_rpc_client_secret_key(rpc_client_secret_key); - } - if (!m_wallet->is_trusted_daemon()) { message_writer(console_color_red, true) << (boost::format(tr("Warning: using an untrusted daemon at %s")) % m_wallet->get_daemon_address()).str(); @@ -4755,7 +4519,6 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet) || command_line::get_arg(vm, arg_restore_from_seed); m_restore_multisig_wallet = command_line::get_arg(vm, arg_restore_multisig_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); - m_allow_mismatched_daemon_version = command_line::get_arg(vm, arg_allow_mismatched_daemon_version); m_restore_height = command_line::get_arg(vm, arg_restore_height); m_restore_date = command_line::get_arg(vm, arg_restore_date); m_do_not_relay = command_line::get_arg(vm, arg_do_not_relay); @@ -4786,12 +4549,20 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version) uint32_t version_ = 0; if (!version) version = &version_; - if (!m_wallet->check_connection(version)) + bool wallet_is_outdated = false, daemon_is_outdated = false; + if (!m_wallet->check_connection(version, NULL, 200000, &wallet_is_outdated, &daemon_is_outdated)) { if (!silent) { if (m_wallet->is_offline()) fail_msg_writer() << tr("wallet failed to connect to daemon, because it is set to offline mode"); + else if (wallet_is_outdated) + fail_msg_writer() << tr("wallet failed to connect to daemon, because it is not up to date. ") << + tr("Please make sure you are running the latest wallet."); + else if (daemon_is_outdated) + fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " << + tr("Daemon is not up to date. " + "Please make sure the daemon is running the latest version or change the daemon address using the 'set_daemon' command."); else fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " << tr("Daemon either is not started or wrong port was passed. " @@ -4799,7 +4570,7 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version) } return false; } - if (!m_allow_mismatched_daemon_version && ((*version >> 16) != CORE_RPC_VERSION_MAJOR)) + if (!m_wallet->is_mismatched_daemon_version_allowed() && ((*version >> 16) != CORE_RPC_VERSION_MAJOR)) { if (!silent) fail_msg_writer() << boost::format(tr("Daemon uses a different RPC major version (%u) than the wallet (%u): %s. Either update one of them, or use --allow-mismatched-daemon-version.")) % (*version>>16) % CORE_RPC_VERSION_MAJOR % m_wallet->get_daemon_address(); @@ -4954,6 +4725,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr "your current session's state. Otherwise, you might need to synchronize \n" "your wallet again (your wallet keys are NOT at risk in any case).\n") ; + success_msg_writer() << tr("Filename: ") << boost::filesystem::complete(m_wallet->get_keys_file()); if (!two_random) { @@ -5057,7 +4829,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr } //---------------------------------------------------------------------------------------------------- boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm, - const epee::wipeable_string &multisig_keys, const std::string &old_language) + const epee::wipeable_string &multisig_keys, const epee::wipeable_string &seed_pass, const std::string &old_language) { std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> rc; try { rc = tools::wallet2::make_new(vm, false, password_prompter); } @@ -5091,7 +4863,16 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr try { - m_wallet->generate(m_wallet_file, std::move(rc.second).password(), multisig_keys, create_address_file); + if (seed_pass.empty()) + m_wallet->generate(m_wallet_file, std::move(rc.second).password(), multisig_keys, create_address_file); + else + { + crypto::secret_key key; + crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key); + sc_reduce32((unsigned char*)key.data); + const epee::wipeable_string &msig_keys = m_wallet->decrypt<epee::wipeable_string>(std::string(multisig_keys.data(), multisig_keys.size()), key, true); + m_wallet->generate(m_wallet_file, std::move(rc.second).password(), msig_keys, create_address_file); + } bool ready; uint32_t threshold, total; if (!m_wallet->multisig(&ready, &threshold, &total) || !ready) @@ -5220,7 +5001,6 @@ bool simple_wallet::close_wallet() if (m_idle_run.load(std::memory_order_relaxed)) { m_idle_run.store(false, std::memory_order_relaxed); - m_suspend_rpc_payment_mining.store(true, std::memory_order_relaxed); m_wallet->stop(); { boost::unique_lock<boost::mutex> lock(m_idle_mutex); @@ -5496,40 +5276,6 @@ bool simple_wallet::stop_mining(const std::vector<std::string>& args) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::check_daemon_rpc_prices(const std::string &daemon_url, uint32_t &actual_cph, uint32_t &claimed_cph) -{ - try - { - auto i = m_claimed_cph.find(daemon_url); - if (i == m_claimed_cph.end()) - return false; - - claimed_cph = m_claimed_cph[daemon_url]; - bool payment_required; - uint64_t credits, diff, credits_per_hash_found, height, seed_height; - uint32_t cookie; - cryptonote::blobdata hashing_blob; - crypto::hash seed_hash, next_seed_hash; - if (m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required) - { - actual_cph = RPC_CREDITS_PER_HASH_SCALE * (credits_per_hash_found / (float)diff); - return true; - } - else - { - fail_msg_writer() << tr("Error checking daemon RPC access prices"); - } - } - catch (const std::exception &e) - { - // can't check - fail_msg_writer() << tr("Error checking daemon RPC access prices: ") << e.what(); - return false; - } - // no record found for this daemon - return false; -} -//---------------------------------------------------------------------------------------------------- bool simple_wallet::set_daemon(const std::vector<std::string>& args) { std::string daemon_url; @@ -5624,20 +5370,6 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args) } success_msg_writer() << boost::format("Daemon set to %s, %s") % daemon_url % (m_wallet->is_trusted_daemon() ? tr("trusted") : tr("untrusted")); - - // check whether the daemon's prices match the claim, and disconnect if not, to disincentivize daemons lying - uint32_t actual_cph, claimed_cph; - if (check_daemon_rpc_prices(daemon_url, actual_cph, claimed_cph)) - { - if (actual_cph < claimed_cph) - { - fail_msg_writer() << tr("Daemon RPC credits/hash is less than was claimed. Either this daemon is cheating, or it changed its setup recently."); - fail_msg_writer() << tr("Claimed: ") << claimed_cph / (float)RPC_CREDITS_PER_HASH_SCALE; - fail_msg_writer() << tr("Actual: ") << actual_cph / (float)RPC_CREDITS_PER_HASH_SCALE; - } - } - - m_daemon_rpc_payment_message_displayed = false; } else { fail_msg_writer() << tr("This does not seem to be a valid daemon URL."); } @@ -5871,7 +5603,10 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo { m_in_manual_refresh.store(true, std::memory_order_relaxed); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);}); - m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks, received_money); + // For manual refresh don't allow incremental checking of the pool: Because we did not process the txs + // for us in the pool during automatic refresh we could miss some of them if we checked the pool + // incrementally here + m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks, received_money, true, false); if (reset == ResetSoftKeepKI) { @@ -5901,10 +5636,9 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo { ss << tr("no connection to daemon. Please make sure daemon is running."); } - catch (const tools::error::payment_required&) + catch (const tools::error::deprecated_rpc_access&) { - ss << tr("payment required."); - m_need_payment = true; + ss << tr("Daemon requires deprecated RPC payment. See https://github.com/monero-project/monero/issues/8722"); } catch (const tools::error::wallet_rpc_error& e) { @@ -6240,10 +5974,9 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args) { fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); } - catch (const tools::error::payment_required&) + catch (const tools::error::deprecated_rpc_access&) { - fail_msg_writer() << tr("payment required."); - m_need_payment = true; + fail_msg_writer() << tr("Daemon requires deprecated RPC payment. See https://github.com/monero-project/monero/issues/8722"); } catch (const tools::error::is_key_image_spent_error&) { @@ -6354,7 +6087,6 @@ bool simple_wallet::process_ring_members(const std::vector<tools::wallet2::pendi } COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); req.get_txid = true; - req.client = cryptonote::make_rpc_payment_signature(m_wallet->get_rpc_client_secret_key()); bool r = m_wallet->invoke_http_bin("/get_outs.bin", req, res); err = interpret_rpc_response(r, res.status); if (!err.empty()) @@ -7912,8 +7644,10 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs) { std::string extra_message; - if (!txs.transfers.second.empty()) - extra_message = (boost::format("%u outputs to import. ") % (unsigned)txs.transfers.second.size()).str(); + if (!std::get<2>(txs.new_transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(txs.new_transfers).size()).str(); + else if (!std::get<2>(txs.transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(txs.transfers).size()).str(); return accept_loaded_tx([&txs](){return txs.txes.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.txes[n];}, extra_message); } //---------------------------------------------------------------------------------------------------- @@ -9248,7 +8982,6 @@ void simple_wallet::wallet_idle_thread() #endif m_refresh_checker.do_call(boost::bind(&simple_wallet::check_refresh, this)); m_mms_checker.do_call(boost::bind(&simple_wallet::check_mms, this)); - m_rpc_payment_checker.do_call(boost::bind(&simple_wallet::check_rpc_payment, this)); if (!m_idle_run.load(std::memory_order_relaxed)) break; @@ -9308,78 +9041,6 @@ bool simple_wallet::check_mms() return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::check_rpc_payment() -{ - if (!m_rpc_payment_mining_requested && m_wallet->auto_mine_for_rpc_payment_threshold() == 0.0f) - return true; - - uint64_t target = m_wallet->credits_target(); - if (target == 0) - target = CREDITS_TARGET; - if (m_rpc_payment_mining_requested) - target = std::numeric_limits<uint64_t>::max(); - bool need_payment = m_need_payment || m_rpc_payment_mining_requested || (m_wallet->credits() < target && m_wallet->daemon_requires_payment()); - if (need_payment) - { - const boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::universal_time(); - auto startfunc = [this](uint64_t diff, uint64_t credits_per_hash_found) - { - const float cph = credits_per_hash_found / (float)diff; - bool low = (diff > MAX_PAYMENT_DIFF || cph < MIN_PAYMENT_RATE); - if (credits_per_hash_found > 0 && cph >= m_wallet->auto_mine_for_rpc_payment_threshold()) - { - MINFO(std::to_string(cph) << " credits per hash is >= our threshold (" << m_wallet->auto_mine_for_rpc_payment_threshold() << "), starting mining"); - return true; - } - else if (m_rpc_payment_mining_requested) - { - MINFO("Mining for RPC payment was requested, starting mining"); - return true; - } - else - { - if (!m_daemon_rpc_payment_message_displayed) - { - success_msg_writer() << boost::format(tr("Daemon requests payment at diff %llu, with %f credits/hash%s. Run start_mining_for_rpc to start mining to pay for RPC access, or use another daemon")) % - diff % cph % (low ? " - this is low" : ""); - m_cmd_binder.print_prompt(); - m_daemon_rpc_payment_message_displayed = true; - } - return false; - } - }; - auto contfunc = [&,this](unsigned n_hashes) - { - if (m_suspend_rpc_payment_mining.load(std::memory_order_relaxed)) - return false; - const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); - m_last_rpc_payment_mining_time = now; - if ((now - start_time).total_microseconds() >= 2 * 1000000) - m_rpc_payment_hash_rate = n_hashes / (float)((now - start_time).total_seconds()); - if ((now - start_time).total_microseconds() >= REFRESH_PERIOD * 1000000) - return false; - return true; - }; - auto foundfunc = [this, target](uint64_t credits) - { - m_need_payment = false; - return credits < target; - }; - auto errorfunc = [this](const std::string &error) - { - fail_msg_writer() << tr("Error mining to daemon: ") << error; - m_cmd_binder.print_prompt(); - }; - bool ret = m_wallet->search_for_rpc_payment(target, m_rpc_payment_threads, startfunc, contfunc, foundfunc, errorfunc); - if (!ret) - { - fail_msg_writer() << tr("Failed to start mining for RPC payment"); - m_cmd_binder.print_prompt(); - } - } - return true; -} -//---------------------------------------------------------------------------------------------------- std::string simple_wallet::get_prompt() const { if (m_locked) @@ -10627,14 +10288,12 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_restore_multisig_wallet ); command_line::add_arg(desc_params, arg_non_deterministic ); command_line::add_arg(desc_params, arg_electrum_seed ); - command_line::add_arg(desc_params, arg_allow_mismatched_daemon_version); command_line::add_arg(desc_params, arg_restore_height); command_line::add_arg(desc_params, arg_restore_date); command_line::add_arg(desc_params, arg_do_not_relay); command_line::add_arg(desc_params, arg_create_address_file); command_line::add_arg(desc_params, arg_subaddress_lookahead); command_line::add_arg(desc_params, arg_use_english_language_names); - command_line::add_arg(desc_params, arg_rpc_client_secret_key); po::positional_options_description positional_options; positional_options.add(arg_command.name, -1); @@ -11157,7 +10816,8 @@ void simple_wallet::mms_next(const std::vector<std::string> &args) mms::message m = ms.get_message_by_id(data.message_ids[i]); sig_args[i] = m.content; } - command_successful = exchange_multisig_keys_main(sig_args, true); + // todo: update mms to enable 'key exchange force updating' + command_successful = exchange_multisig_keys_main(sig_args, false, true); break; } diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 6a9fa149d..2a5d7f2b6 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -101,7 +101,7 @@ namespace cryptonote boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, const boost::optional<crypto::secret_key>& spendkey, const crypto::secret_key& viewkey); boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, - const epee::wipeable_string &multisig_keys, const std::string &old_language); + const epee::wipeable_string &multisig_keys, const epee::wipeable_string &seed_pass, const std::string &old_language); boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm); boost::optional<epee::wipeable_string> open_wallet(const boost::program_options::variables_map& vm); bool close_wallet(); @@ -154,9 +154,6 @@ namespace cryptonote bool set_export_format(const std::vector<std::string> &args = std::vector<std::string>()); bool set_load_deprecated_formats(const std::vector<std::string> &args = std::vector<std::string>()); bool set_enable_multisig(const std::vector<std::string> &args = std::vector<std::string>()); - bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>()); - bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>()); - bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool apropos(const std::vector<std::string> &args); bool scan_tx(const std::vector<std::string> &args); @@ -235,7 +232,7 @@ namespace cryptonote bool make_multisig(const std::vector<std::string>& args); bool make_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool exchange_multisig_keys(const std::vector<std::string> &args); - bool exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms); + bool exchange_multisig_keys_main(const std::vector<std::string> &args, const bool force_update_use_with_caution, const bool called_by_mms); bool export_multisig(const std::vector<std::string>& args); bool export_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool import_multisig(const std::vector<std::string>& args); @@ -258,9 +255,6 @@ namespace cryptonote bool thaw(const std::vector<std::string>& args); bool frozen(const std::vector<std::string>& args); bool lock(const std::vector<std::string>& args); - bool rpc_payment_info(const std::vector<std::string> &args); - bool start_mining_for_rpc(const std::vector<std::string> &args); - bool stop_mining_for_rpc(const std::vector<std::string> &args); bool show_qr_code(const std::vector<std::string> &args); bool net_stats(const std::vector<std::string>& args); bool public_nodes(const std::vector<std::string>& args); @@ -338,7 +332,6 @@ namespace cryptonote bool check_inactivity(); bool check_refresh(); bool check_mms(); - bool check_rpc_payment(); void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon); @@ -429,7 +422,6 @@ namespace cryptonote bool m_restore_deterministic_wallet; // recover flag bool m_restore_multisig_wallet; // recover flag bool m_non_deterministic; // old 2-random generation - bool m_allow_mismatched_daemon_version; bool m_restoring; // are we restoring, by whatever method? uint64_t m_restore_height; // optional bool m_do_not_relay; @@ -459,17 +451,6 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<1> m_inactivity_checker; epee::math_helper::once_a_time_seconds_range<get_random_interval<80 * 1000000, 100 * 1000000>> m_refresh_checker; epee::math_helper::once_a_time_seconds_range<get_random_interval<90 * 1000000, 110 * 1000000>> m_mms_checker; - epee::math_helper::once_a_time_seconds_range<get_random_interval<90 * 1000000, 115 * 1000000>> m_rpc_payment_checker; - - std::atomic<bool> m_need_payment; - boost::posix_time::ptime m_last_rpc_payment_mining_time; - bool m_rpc_payment_mining_requested; - uint32_t m_rpc_payment_threads = 0; - bool m_daemon_rpc_payment_message_displayed; - float m_rpc_payment_hash_rate; - std::atomic<bool> m_suspend_rpc_payment_mining; - - std::unordered_map<std::string, uint32_t> m_claimed_cph; // MMS mms::message_store& get_message_store() const { return m_wallet->get_message_store(); }; diff --git a/src/version.cpp.in b/src/version.cpp.in index c6d473bf9..91fdc9902 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.18.0.0" +#define DEF_MONERO_VERSION "0.18.1.0" #define DEF_MONERO_RELEASE_NAME "Fluorine Fermi" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG #define DEF_MONERO_VERSION_IS_RELEASE @VERSION_IS_RELEASE@ diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 6095f99d5..48c7f1c5b 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # @@ -37,7 +37,6 @@ set(wallet_sources node_rpc_proxy.cpp message_store.cpp message_transporter.cpp - wallet_rpc_payments.cpp ) monero_find_all_headers(wallet_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index af7948d8a..35ce5144b 100644 --- a/src/wallet/api/CMakeLists.txt +++ b/src/wallet/api/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2022, The Monero Project +# Copyright (c) 2014-2023, The Monero Project # # All rights reserved. # diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp index c73653e37..aeffe921e 100644 --- a/src/wallet/api/address_book.cpp +++ b/src/wallet/api/address_book.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/address_book.h b/src/wallet/api/address_book.h index 5b0655000..8de7f95ff 100644 --- a/src/wallet/api/address_book.h +++ b/src/wallet/api/address_book.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 70a702796..47eb7a243 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 0a9779c07..ea2831b25 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress.cpp b/src/wallet/api/subaddress.cpp index 9e358b4c8..7a1460c8e 100644 --- a/src/wallet/api/subaddress.cpp +++ b/src/wallet/api/subaddress.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress.h b/src/wallet/api/subaddress.h index 53ece126d..bcee10577 100644 --- a/src/wallet/api/subaddress.h +++ b/src/wallet/api/subaddress.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress_account.cpp b/src/wallet/api/subaddress_account.cpp index e8153df3d..ebee80e7e 100644 --- a/src/wallet/api/subaddress_account.cpp +++ b/src/wallet/api/subaddress_account.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/subaddress_account.h b/src/wallet/api/subaddress_account.h index 94cab47fb..3934df3ef 100644 --- a/src/wallet/api/subaddress_account.h +++ b/src/wallet/api/subaddress_account.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 9f5e41156..ad797cc07 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_history.h b/src/wallet/api/transaction_history.h index 1d52f4a69..04bc8a705 100644 --- a/src/wallet/api/transaction_history.h +++ b/src/wallet/api/transaction_history.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_info.cpp b/src/wallet/api/transaction_info.cpp index 572b04316..8f5ee39a0 100644 --- a/src/wallet/api/transaction_info.cpp +++ b/src/wallet/api/transaction_info.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h index 6337f2aaa..33dc8a7f4 100644 --- a/src/wallet/api/transaction_info.h +++ b/src/wallet/api/transaction_info.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp index 6165a2240..07cf93f59 100644 --- a/src/wallet/api/unsigned_transaction.cpp +++ b/src/wallet/api/unsigned_transaction.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h index 30065a7fa..4fd1a0b28 100644 --- a/src/wallet/api/unsigned_transaction.h +++ b/src/wallet/api/unsigned_transaction.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/utils.cpp b/src/wallet/api/utils.cpp index d8dcedc5f..d02fdcaf6 100644 --- a/src/wallet/api/utils.cpp +++ b/src/wallet/api/utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 1ee2e20b6..0c3aaf853 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -63,8 +63,8 @@ namespace { static const int MAX_REFRESH_INTERVAL_MILLIS = 1000 * 60 * 1; // Default refresh interval when connected to remote node static const int DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS = 1000 * 10; - // Connection timeout 30 sec - static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30; + // Connection timeout 20 sec + static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 20; std::string get_default_ringdb_path(cryptonote::network_type nettype) { @@ -209,38 +209,6 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback // TODO; } - // Light wallet callbacks - virtual void on_lw_new_block(uint64_t height) - { - if (m_listener) { - m_listener->newBlock(height); - } - } - - virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) - { - if (m_listener) { - std::string tx_hash = epee::string_tools::pod_to_hex(txid); - m_listener->moneyReceived(tx_hash, amount); - } - } - - virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) - { - if (m_listener) { - std::string tx_hash = epee::string_tools::pod_to_hex(txid); - m_listener->unconfirmedMoneyReceived(tx_hash, amount); - } - } - - virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) - { - if (m_listener) { - std::string tx_hash = epee::string_tools::pod_to_hex(txid); - m_listener->moneySpent(tx_hash, amount); - } - } - virtual void on_device_button_request(uint64_t code) { if (m_listener) { @@ -535,7 +503,7 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas view_wallet->generate(path, password, address, viewkey); // Export/Import outputs - auto outputs = m_wallet->export_outputs(); + auto outputs = m_wallet->export_outputs(true/*all*/); view_wallet->import_outputs(outputs); // Copy scanned blockchain @@ -553,7 +521,7 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas // Export/Import key images // We already know the spent status from the outputs we exported, thus no need to check them again - auto key_images = m_wallet->export_key_images(); + auto key_images = m_wallet->export_key_images(true/*all*/); uint64_t spent = 0; uint64_t unspent = 0; view_wallet->import_key_images(key_images.second, key_images.first, spent, unspent, false); @@ -950,42 +918,11 @@ string WalletImpl::keysFilename() const bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit, const std::string &daemon_username, const std::string &daemon_password, bool use_ssl, bool lightWallet, const std::string &proxy_address) { clearStatus(); - m_wallet->set_light_wallet(lightWallet); if(daemon_username != "") m_daemon_login.emplace(daemon_username, daemon_password); return doInit(daemon_address, proxy_address, upper_transaction_size_limit, use_ssl); } -bool WalletImpl::lightWalletLogin(bool &isNewWallet) const -{ - return m_wallet->light_wallet_login(isNewWallet); -} - -bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) -{ - try - { - tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::response response; - if(!m_wallet->light_wallet_import_wallet_request(response)){ - setStatusError(tr("Failed to send import wallet request")); - return false; - } - fee = response.import_fee; - payment_id = response.payment_id; - new_request = response.new_request; - request_fulfilled = response.request_fulfilled; - payment_address = response.payment_address; - status = response.status; - } - catch (const std::exception &e) - { - LOG_ERROR("Error sending import wallet request: " << e.what()); - setStatusError(e.what()); - return false; - } - return true; -} - void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height) { m_wallet->set_refresh_from_block_height(refresh_from_block_height); @@ -1018,9 +955,6 @@ uint64_t WalletImpl::unlockedBalance(uint32_t accountIndex) const uint64_t WalletImpl::blockChainHeight() const { - if(m_wallet->light_wallet()) { - return m_wallet->get_light_wallet_scanned_block_height(); - } return m_wallet->get_blockchain_current_height(); } uint64_t WalletImpl::approximateBlockChainHeight() const @@ -1035,9 +969,6 @@ uint64_t WalletImpl::estimateBlockChainHeight() const uint64_t WalletImpl::daemonBlockChainHeight() const { - if(m_wallet->light_wallet()) { - return m_wallet->get_light_wallet_scanned_block_height(); - } if (!m_is_connected) return 0; std::string err; @@ -1054,9 +985,6 @@ uint64_t WalletImpl::daemonBlockChainHeight() const uint64_t WalletImpl::daemonBlockChainTargetHeight() const { - if(m_wallet->light_wallet()) { - return m_wallet->get_light_wallet_blockchain_height(); - } if (!m_is_connected) return 0; std::string err; @@ -1146,8 +1074,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file // Check tx data and construct confirmation message std::string extra_message; - if (!transaction->m_unsigned_tx_set.transfers.second.empty()) - extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.second.size()).str(); + if (!std::get<2>(transaction->m_unsigned_tx_set.transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(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); setStatus(transaction->status(), transaction->errorString()); @@ -1302,11 +1230,15 @@ bool WalletImpl::scanTransactions(const std::vector<std::string> &txids) } txids_u.insert(txid); } - std::vector<crypto::hash> txids_v(txids_u.begin(), txids_u.end()); try { - m_wallet->scan_tx(txids_v); + m_wallet->scan_tx(txids_u); + } + catch (const tools::error::wont_reprocess_recent_txs_via_untrusted_daemon &e) + { + setStatusError(e.what()); + return false; } catch (const std::exception &e) { @@ -1396,12 +1328,12 @@ string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t thres return string(); } -std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &info) { +std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution /*= false*/) { try { clearStatus(); checkMultisigWalletNotReady(m_wallet); - return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info); + return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info, force_update_use_with_caution); } catch (const exception& e) { LOG_ERROR("Error on exchanging multisig keys: " << e.what()); setStatusError(string(tr("Failed to exchange multisig keys: ")) + e.what()); @@ -1782,7 +1714,7 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0), m_wallet->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, 0), m_wallet->use_fork_rules(HF_VERSION_VIEW_TAGS, 0), - m_wallet->get_base_fee(), + m_wallet->get_base_fee(priority), m_wallet->get_fee_quantization_mask()); } @@ -2173,11 +2105,16 @@ bool WalletImpl::connectToDaemon() Wallet::ConnectionStatus WalletImpl::connected() const { uint32_t version = 0; - m_is_connected = m_wallet->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); + bool wallet_is_outdated = false, daemon_is_outdated = false; + m_is_connected = m_wallet->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS, &wallet_is_outdated, &daemon_is_outdated); if (!m_is_connected) - return Wallet::ConnectionStatus_Disconnected; - // Version check is not implemented in light wallets nodes/wallets - if (!m_wallet->light_wallet() && (version >> 16) != CORE_RPC_VERSION_MAJOR) + { + if (wallet_is_outdated || daemon_is_outdated) + return Wallet::ConnectionStatus_WrongVersion; + else + return Wallet::ConnectionStatus_Disconnected; + } + if ((version >> 16) != CORE_RPC_VERSION_MAJOR) return Wallet::ConnectionStatus_WrongVersion; return Wallet::ConnectionStatus_Connected; } @@ -2271,7 +2208,7 @@ void WalletImpl::doRefresh() LOG_PRINT_L3(__FUNCTION__ << ": doRefresh, rescan = "<<rescan); // Syncing daemon and refreshing wallet simultaneously is very resource intensive. // Disable refresh if wallet is disconnected or daemon isn't synced. - if (m_wallet->light_wallet() || daemonSynced()) { + if (daemonSynced()) { if(rescan) m_wallet->rescan_blockchain(false); m_wallet->refresh(trustedDaemon()); @@ -2362,7 +2299,6 @@ bool WalletImpl::doInit(const string &daemon_address, const std::string &proxy_a // in case new wallet, this will force fast-refresh (pulling hashes instead of blocks) // If daemon isn't synced a calculated block height will be used instead - //TODO: Handle light wallet scenario where block height = 0. if (isNewWallet() && daemonSynced()) { LOG_PRINT_L2(__FUNCTION__ << ":New Wallet - fast refresh until " << daemonBlockChainHeight()); m_wallet->set_refresh_from_block_height(daemonBlockChainHeight()); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 018b2a0ed..d1bf4f759 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -39,6 +39,7 @@ #include <boost/thread/thread.hpp> #include <boost/thread/condition_variable.hpp> +class WalletApiAccessorTest; namespace Monero { class TransactionHistoryImpl; @@ -146,7 +147,7 @@ public: MultisigState multisig() const override; std::string getMultisigInfo() const override; std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override; - std::string exchangeMultisigKeys(const std::vector<std::string> &info) override; + std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution = false) override; bool exportMultisigImages(std::string& images) override; size_t importMultisigImages(const std::vector<std::string>& images) override; bool hasMultisigPartialKeyImages() const override; @@ -207,8 +208,6 @@ public: 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) override; virtual std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) const override; virtual std::string getDefaultDataDir() const override; - virtual bool lightWalletLogin(bool &isNewWallet) const override; - virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) override; virtual bool blackballOutputs(const std::vector<std::string> &outputs, bool add) override; virtual bool blackballOutput(const std::string &amount, const std::string &offset) override; virtual bool unblackballOutput(const std::string &amount, const std::string &offset) override; @@ -248,6 +247,7 @@ private: friend class AddressBookImpl; friend class SubaddressImpl; friend class SubaddressAccountImpl; + friend class ::WalletApiAccessorTest; std::unique_ptr<tools::wallet2> m_wallet; mutable boost::mutex m_statusMutex; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index b67bce60c..df86da847 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -38,6 +38,7 @@ #include <ctime> #include <iostream> #include <stdexcept> +#include <cstdint> // Public interface for libwallet library namespace Monero { @@ -541,7 +542,7 @@ struct Wallet * \param upper_transaction_size_limit * \param daemon_username * \param daemon_password - * \param lightWallet - start wallet in light mode, connect to a openmonero compatible server. + * \param lightWallet - deprecated * \param proxy_address - set proxy address, empty string to disable * \return - true on success */ @@ -796,9 +797,10 @@ struct Wallet /** * @brief exchange_multisig_keys - provides additional key exchange round for arbitrary multisig schemes (like N-1/N, M/N) * @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call + * @param force_update_use_with_caution - force multisig account to update even if not all signers contribute round messages * @return new info string if more rounds required or an empty string if wallet creation is done */ - virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info) = 0; + virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution) = 0; /** * @brief exportMultisigImages - exports transfers' key images * @param images - output paramter for hex encoded array of images @@ -1064,12 +1066,6 @@ struct Wallet //! secondary key reuse mitigation virtual void keyReuseMitigation2(bool mitigation) = 0; - //! Light wallet authenticate and login - virtual bool lightWalletLogin(bool &isNewWallet) const = 0; - - //! Initiates a light wallet import wallet request - virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) = 0; - //! locks/unlocks the keys file; returns true on success virtual bool lockKeysFile() = 0; virtual bool unlockKeysFile() = 0; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index e81b8f83a..1bb4bc27c 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index a223e1df9..46ec36297 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/message_store.cpp b/src/wallet/message_store.cpp index cf1d91d5a..1169d5269 100644 --- a/src/wallet/message_store.cpp +++ b/src/wallet/message_store.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/message_store.h b/src/wallet/message_store.h index c5421a702..202d77be6 100644 --- a/src/wallet/message_store.h +++ b/src/wallet/message_store.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/message_transporter.cpp b/src/wallet/message_transporter.cpp index c985eb583..7aa765c87 100644 --- a/src/wallet/message_transporter.cpp +++ b/src/wallet/message_transporter.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/message_transporter.h b/src/wallet/message_transporter.h index b7d3c8107..d24888333 100644 --- a/src/wallet/message_transporter.h +++ b/src/wallet/message_transporter.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 7810abdd2..1142e46ce 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -28,8 +28,6 @@ #include "node_rpc_proxy.h" #include "rpc/core_rpc_server_commands_defs.h" -#include "rpc/rpc_payment_signature.h" -#include "rpc/rpc_payment_costs.h" #include "storages/http_abstract_invoke.h" #include <boost/thread.hpp> @@ -37,7 +35,6 @@ #define RETURN_ON_RPC_RESPONSE_ERROR(r, error, res, method) \ do { \ CHECK_AND_ASSERT_MES(error.code == 0, error.message, error.message); \ - handle_payment_changes(res, std::integral_constant<bool, HasCredits<decltype(res)>::Has>()); \ CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon"); \ /* empty string -> not connection */ \ CHECK_AND_ASSERT_MES(!res.status.empty(), res.status, "No connection to daemon"); \ @@ -53,9 +50,8 @@ namespace tools static const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30); -NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::abstract_http_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex) +NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::abstract_http_client &http_client, boost::recursive_mutex &mutex) : m_http_client(http_client) - , m_rpc_payment_state(rpc_payment_state) , m_daemon_rpc_mutex(mutex) , m_offline(false) { @@ -77,23 +73,18 @@ void NodeRPCProxy::invalidate() m_block_weight_limit = 0; m_adjusted_time = 0; m_get_info_time = 0; - m_rpc_payment_info_time = 0; - m_rpc_payment_seed_height = 0; - m_rpc_payment_seed_hash = crypto::null_hash; - m_rpc_payment_next_seed_hash = crypto::null_hash; m_height_time = 0; - m_rpc_payment_diff = 0; - m_rpc_payment_credits_per_hash_found = 0; - m_rpc_payment_height = 0; - m_rpc_payment_cookie = 0; + m_target_height_time = 0; + m_daemon_hard_forks.clear(); } -boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) +boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version, std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, uint64_t &height, uint64_t &target_height) { if (m_offline) return boost::optional<std::string>("offline"); if (m_rpc_version == 0) { + const time_t now = time(NULL); cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t); { @@ -101,9 +92,28 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout); RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_version"); } + m_rpc_version = resp_t.version; + m_daemon_hard_forks.clear(); + for (const auto &hf : resp_t.hard_forks) + m_daemon_hard_forks.push_back(std::make_pair(hf.hf_version, hf.height)); + if (resp_t.current_height > 0 || resp_t.target_height > 0) + { + m_height = resp_t.current_height; + m_target_height = resp_t.target_height; + m_height_time = now; + m_target_height_time = now; + } } + rpc_version = m_rpc_version; + daemon_hard_forks = m_daemon_hard_forks; + boost::optional<std::string> result = get_height(height); + if (result) + return result; + result = get_target_height(target_height); + if (result) + return result; return boost::optional<std::string>(); } @@ -125,11 +135,8 @@ boost::optional<std::string> NodeRPCProxy::get_info() { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client, rpc_timeout); RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_info"); - check_rpc_cost(m_rpc_payment_state, "get_info", resp_t.credits, pre_call_credits, COST_PER_GET_INFO); } m_height = resp_t.height; @@ -138,6 +145,7 @@ boost::optional<std::string> NodeRPCProxy::get_info() m_adjusted_time = resp_t.adjusted_time; m_get_info_time = now; m_height_time = now; + m_target_height_time = now; } return boost::optional<std::string>(); } @@ -160,6 +168,13 @@ boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) { + const time_t now = time(NULL); + if (now < m_target_height_time + 30) // re-cache every 30 seconds + { + height = m_target_height; + return boost::optional<std::string>(); + } + auto res = get_info(); if (res) return res; @@ -197,11 +212,8 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout); RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "hard_fork_info"); - check_rpc_cost(m_rpc_payment_state, "hard_fork_info", resp_t.credits, pre_call_credits, COST_PER_HARD_FORK_INFO); } m_earliest_height[version] = resp_t.earliest_height; @@ -229,11 +241,8 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate_2021_sc { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout); RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_fee_estimate"); - check_rpc_cost(m_rpc_payment_state, "get_fee_estimate", resp_t.credits, pre_call_credits, COST_PER_FEE_ESTIMATE); } m_dynamic_base_fee_estimate = resp_t.fee; @@ -275,11 +284,8 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout); RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_fee_estimate"); - check_rpc_cost(m_rpc_payment_state, "get_fee_estimate", resp_t.credits, pre_call_credits, COST_PER_FEE_ESTIMATE); } m_dynamic_base_fee_estimate = resp_t.fee; @@ -296,70 +302,49 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f return boost::optional<std::string>(); } -boost::optional<std::string> NodeRPCProxy::get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie) +boost::optional<std::string> NodeRPCProxy::get_transactions(const std::vector<crypto::hash> &txids, const std::function<void(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request&, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response&, bool)> &f) { - const time_t now = time(NULL); - if (m_rpc_payment_state.stale || now >= m_rpc_payment_info_time + 5*60 || (mining && now >= m_rpc_payment_info_time + 10)) // re-cache every 10 seconds if mining, 5 minutes otherwise + const size_t SLICE_SIZE = 100; // RESTRICTED_TRANSACTIONS_COUNT as defined in rpc/core_rpc_server.cpp + for (size_t offset = 0; offset < txids.size(); offset += SLICE_SIZE) { - cryptonote::COMMAND_RPC_ACCESS_INFO::request req_t = AUTO_VAL_INIT(req_t); - cryptonote::COMMAND_RPC_ACCESS_INFO::response resp_t = AUTO_VAL_INIT(resp_t); + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response resp_t = AUTO_VAL_INIT(resp_t); + const size_t n_txids = std::min<size_t>(SLICE_SIZE, txids.size() - offset); + for (size_t n = offset; n < (offset + n_txids); ++n) + req_t.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids[n])); + MDEBUG("asking for " << req_t.txs_hashes.size() << " transactions"); + req_t.decode_as_json = false; + req_t.prune = true; + + bool r = false; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_info", req_t, resp_t, m_http_client, rpc_timeout); - RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "rpc_access_info"); - m_rpc_payment_state.stale = false; + r = net_utils::invoke_http_json("/gettransactions", req_t, resp_t, m_http_client, rpc_timeout); } - m_rpc_payment_diff = resp_t.diff; - m_rpc_payment_credits_per_hash_found = resp_t.credits_per_hash_found; - m_rpc_payment_height = resp_t.height; - m_rpc_payment_seed_height = resp_t.seed_height; - m_rpc_payment_cookie = resp_t.cookie; + f(req_t, resp_t, r); + } + return boost::optional<std::string>(); +} - if (m_rpc_payment_diff == 0) - { - // If no payment required daemon doesn't give us back a hashing blob - m_rpc_payment_blob.clear(); - } - else if (!epee::string_tools::parse_hexstr_to_binbuff(resp_t.hashing_blob, m_rpc_payment_blob) || m_rpc_payment_blob.size() < 43) - { - MERROR("Invalid hashing blob: " << resp_t.hashing_blob); - return std::string("Invalid hashing blob"); - } - if (resp_t.seed_hash.empty()) - { - m_rpc_payment_seed_hash = crypto::null_hash; - } - else if (!epee::string_tools::hex_to_pod(resp_t.seed_hash, m_rpc_payment_seed_hash)) - { - MERROR("Invalid seed_hash: " << resp_t.seed_hash); - return std::string("Invalid seed hash"); - } - if (resp_t.next_seed_hash.empty()) - { - m_rpc_payment_next_seed_hash = crypto::null_hash; - } - else if (!epee::string_tools::hex_to_pod(resp_t.next_seed_hash, m_rpc_payment_next_seed_hash)) - { - MERROR("Invalid next_seed_hash: " << resp_t.next_seed_hash); - return std::string("Invalid next seed hash"); - } - m_rpc_payment_info_time = now; +boost::optional<std::string> NodeRPCProxy::get_block_header_by_height(uint64_t height, cryptonote::block_header_response &block_header) +{ + if (m_offline) + return boost::optional<std::string>("offline"); + + cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response resp_t = AUTO_VAL_INIT(resp_t); + req_t.height = height; + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req_t, resp_t, m_http_client, rpc_timeout); + RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "getblockheaderbyheight"); } - payment_required = m_rpc_payment_diff > 0; - credits = m_rpc_payment_state.credits; - diff = m_rpc_payment_diff; - credits_per_hash_found = m_rpc_payment_credits_per_hash_found; - blob = m_rpc_payment_blob; - height = m_rpc_payment_height; - seed_height = m_rpc_payment_seed_height; - seed_hash = m_rpc_payment_seed_hash; - next_seed_hash = m_rpc_payment_next_seed_hash; - cookie = m_rpc_payment_cookie; - return boost::none; + block_header = std::move(resp_t.block_header); + return boost::optional<std::string>(); } } diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 07675cdb0..0dcfd0f83 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, The Monero Project +// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // @@ -34,7 +34,6 @@ #include "include_base_utils.h" #include "net/abstract_http_client.h" #include "rpc/core_rpc_server_commands_defs.h" -#include "wallet_rpc_helpers.h" namespace tools { @@ -42,13 +41,12 @@ namespace tools class NodeRPCProxy { public: - NodeRPCProxy(epee::net_utils::http::abstract_http_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex); + NodeRPCProxy(epee::net_utils::http::abstract_http_client &http_client, boost::recursive_mutex &mutex); - void set_client_secret_key(const crypto::secret_key &skey) { m_client_id_secret_key = skey; } void invalidate(); void set_offline(bool offline) { m_offline = offline; } - boost::optional<std::string> get_rpc_version(uint32_t &version); + boost::optional<std::string> get_rpc_version(uint32_t &rpc_version, std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, uint64_t &height, uint64_t &target_height); boost::optional<std::string> get_height(uint64_t &height); void set_height(uint64_t h); boost::optional<std::string> get_target_height(uint64_t &height); @@ -58,27 +56,14 @@ public: boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee); boost::optional<std::string> get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees); boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask); - boost::optional<std::string> get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie); - -private: - template<typename T> void handle_payment_changes(const T &res, std::true_type) { - if (res.status == CORE_RPC_STATUS_OK || res.status == CORE_RPC_STATUS_PAYMENT_REQUIRED) - m_rpc_payment_state.credits = res.credits; - if (res.top_hash != m_rpc_payment_state.top_hash) - { - m_rpc_payment_state.top_hash = res.top_hash; - m_rpc_payment_state.stale = true; - } - } - template<typename T> void handle_payment_changes(const T &res, std::false_type) {} + boost::optional<std::string> get_transactions(const std::vector<crypto::hash> &txids, const std::function<void(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request&, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response&, bool)> &f); + boost::optional<std::string> get_block_header_by_height(uint64_t height, cryptonote::block_header_response &block_header); private: boost::optional<std::string> get_info(); epee::net_utils::http::abstract_http_client &m_http_client; - rpc_payment_state_t &m_rpc_payment_state; boost::recursive_mutex &m_daemon_rpc_mutex; - crypto::secret_key m_client_id_secret_key; bool m_offline; uint64_t m_height; @@ -93,16 +78,9 @@ private: uint64_t m_target_height; uint64_t m_block_weight_limit; time_t m_get_info_time; - time_t m_rpc_payment_info_time; - uint64_t m_rpc_payment_diff; - uint64_t m_rpc_payment_credits_per_hash_found; - cryptonote::blobdata m_rpc_payment_blob; - uint64_t m_rpc_payment_height; - uint64_t m_rpc_payment_seed_height; - crypto::hash m_rpc_payment_seed_hash; - crypto::hash m_rpc_payment_next_seed_hash; - uint32_t m_rpc_payment_cookie; time_t m_height_time; + time_t m_target_height_time; + std::vector<std::pair<uint8_t, uint64_t>> m_daemon_hard_forks; }; } diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 7e4f12f5b..3b3a8303a 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index bdecdba37..93be362d0 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Monero Project +// Copyright (c) 2018-2023, The Monero Project // // All rights reserved. diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 195763949..b9cae0cac 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -39,7 +39,9 @@ #include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/predicate.hpp> +#include <boost/archive/binary_iarchive.hpp> #include <boost/asio/ip/address.hpp> +#include <boost/filesystem/operations.hpp> #include <boost/range/adaptor/transformed.hpp> #include <boost/preprocessor/stringize.hpp> #include <openssl/evp.h> @@ -47,23 +49,20 @@ using namespace epee; #include "cryptonote_config.h" +#include "hardforks/hardforks.h" #include "cryptonote_core/tx_sanity_check.h" -#include "wallet_rpc_helpers.h" #include "wallet2.h" #include "wallet_args.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "net/parse.h" #include "rpc/core_rpc_server_commands_defs.h" #include "rpc/core_rpc_server_error_codes.h" -#include "rpc/rpc_payment_signature.h" -#include "rpc/rpc_payment_costs.h" #include "misc_language.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "multisig/multisig.h" #include "multisig/multisig_account.h" #include "multisig/multisig_kex_msg.h" #include "multisig/multisig_tx_builder_ringct.h" -#include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" #include "int-util.h" @@ -275,6 +274,7 @@ struct options { const command_line::arg_descriptor<bool> no_dns = {"no-dns", tools::wallet2::tr("Do not use DNS"), false}; const command_line::arg_descriptor<bool> offline = {"offline", tools::wallet2::tr("Do not connect to a daemon, nor use DNS"), false}; const command_line::arg_descriptor<std::string> extra_entropy = {"extra-entropy", tools::wallet2::tr("File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)")}; + const command_line::arg_descriptor<bool> allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", tools::wallet2::tr("Allow communicating with a daemon that uses a different version"), false}; }; void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file, std::string &mms_file) @@ -484,6 +484,9 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl add_extra_entropy_thread_safe(data.data(), data.size()); } + if (command_line::has_arg(vm, opts.allow_mismatched_daemon_version)) + wallet->allow_mismatched_daemon_version(true); + try { if (!command_line::is_arg_defaulted(vm, opts.tx_notify)) @@ -995,12 +998,12 @@ gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets, double shap rct_offsets(rct_offsets) { gamma = std::gamma_distribution<double>(shape, scale); - THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, error::wallet_internal_error, "Bad offset calculation"); + THROW_WALLET_EXCEPTION_IF(rct_offsets.size() < std::max(1, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE), error::wallet_internal_error, "Bad offset calculation"); const size_t blocks_in_a_year = 86400 * 365 / DIFFICULTY_TARGET_V2; const size_t blocks_to_consider = std::min<size_t>(rct_offsets.size(), blocks_in_a_year); const size_t outputs_to_consider = rct_offsets.back() - (blocks_to_consider < rct_offsets.size() ? rct_offsets[rct_offsets.size() - blocks_to_consider - 1] : 0); begin = rct_offsets.data(); - end = rct_offsets.data() + rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE; + end = rct_offsets.data() + rct_offsets.size() - (std::max(1, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE) - 1); num_rct_outputs = *(end - 1); THROW_WALLET_EXCEPTION_IF(num_rct_outputs == 0, error::wallet_internal_error, "No rct outputs"); average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / static_cast<double>(outputs_to_consider); // this assumes constant target over the whole rct range @@ -1165,6 +1168,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_first_refresh_done(false), m_refresh_from_block_height(0), m_explicit_refresh_from_block_height(true), + m_skip_to_height(0), m_confirm_non_default_ring_size(true), m_ask_password(AskPasswordToDecrypt), m_max_reorg_depth(ORPHANED_BLOCKS_MAX_COUNT), @@ -1185,24 +1189,16 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_show_wallet_name_when_locked(false), m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT), m_setup_background_mining(BackgroundMiningMaybe), - m_persistent_rpc_client_id(false), - m_auto_mine_for_rpc_payment_threshold(-1.0f), m_is_initialized(false), m_kdf_rounds(kdf_rounds), is_old_file_format(false), m_watch_only(false), m_multisig(false), m_multisig_threshold(0), - m_node_rpc_proxy(*m_http_client, m_rpc_payment_state, m_daemon_rpc_mutex), + m_node_rpc_proxy(*m_http_client, m_daemon_rpc_mutex), m_account_public_address{crypto::null_pkey, crypto::null_pkey}, m_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR), m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR), - m_light_wallet(false), - m_light_wallet_scanned_block_height(0), - m_light_wallet_blockchain_height(0), - m_light_wallet_connected(false), - m_light_wallet_balance(0), - m_light_wallet_unlocked_balance(0), m_original_keys_available(false), m_message_store(http_client_factory->create()), m_key_device_type(hw::device::device_type::SOFTWARE), @@ -1217,10 +1213,11 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_rpc_version(0), m_export_format(ExportFormat::Binary), m_load_deprecated_formats(false), - m_credits_target(0), - m_enable_multisig(false) + m_enable_multisig(false), + m_pool_info_query_time(0), + m_has_ever_refreshed_from_node(false), + m_allow_mismatched_daemon_version(false) { - set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); } wallet2::~wallet2() @@ -1278,6 +1275,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.no_dns); command_line::add_arg(desc_params, opts.offline); command_line::add_arg(desc_params, opts.extra_entropy); + command_line::add_arg(desc_params, opts.allow_mismatched_daemon_version); } std::pair<std::unique_ptr<wallet2>, tools::password_container> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) @@ -1333,12 +1331,9 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u m_trusted_daemon = trusted_daemon; if (changed) { - if (!m_persistent_rpc_client_id) { - set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); - } - m_rpc_payment_state.expected_spent = 0; - m_rpc_payment_state.discrepancy = 0; + m_rpc_version = 0; m_node_rpc_proxy.invalidate(); + m_pool_info_query_time = 0; } const std::string address = get_daemon_address(); @@ -1615,14 +1610,13 @@ std::string wallet2::get_subaddress_label(const cryptonote::subaddress_index& in return m_subaddress_labels[index.major][index.minor]; } //---------------------------------------------------------------------------------------------------- -void wallet2::scan_tx(const std::vector<crypto::hash> &txids) +wallet2::tx_entry_data wallet2::get_tx_entries(const std::unordered_set<crypto::hash> &txids) { - // Get the transactions from daemon in batches and add them to a priority queue ordered in chronological order - auto cmp_tx_entry = [](const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry& l, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry& r) - { return l.block_height > r.block_height; }; + tx_entry_data tx_entries; + tx_entries.tx_entries.reserve(txids.size()); - std::priority_queue<cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry, std::vector<COMMAND_RPC_GET_TRANSACTIONS::entry>, decltype(cmp_tx_entry)> txq(cmp_tx_entry); const size_t SLICE_SIZE = 100; // RESTRICTED_TRANSACTIONS_COUNT as defined in rpc/core_rpc_server.cpp, hardcoded in daemon code + std::unordered_set<crypto::hash>::const_iterator it = txids.begin(); for(size_t slice = 0; slice < txids.size(); slice += SLICE_SIZE) { cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); @@ -1631,28 +1625,267 @@ void wallet2::scan_tx(const std::vector<crypto::hash> &txids) size_t ntxes = slice + SLICE_SIZE > txids.size() ? txids.size() - slice : SLICE_SIZE; for (size_t i = slice; i < slice + ntxes; ++i) - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids[i])); + { + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(*it)); + ++it; + } { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - req.client = get_client_signature(); bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to get transaction from daemon"); THROW_WALLET_EXCEPTION_IF(res.txs.size() != req.txs_hashes.size(), error::wallet_internal_error, "Failed to get transaction from daemon"); } for (auto& tx_info : res.txs) - txq.push(tx_info); + { + if (!tx_info.in_pool) + { + tx_entries.lowest_height = std::min(tx_info.block_height, tx_entries.lowest_height); + tx_entries.highest_height = std::max(tx_info.block_height, tx_entries.highest_height); + } + + cryptonote::transaction tx; + crypto::hash tx_hash; + THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(tx_info, tx, tx_hash), error::wallet_internal_error, "Failed to get transaction from daemon"); + tx_entries.tx_entries.emplace_back(process_tx_entry_t{ std::move(tx_info), std::move(tx), std::move(tx_hash) }); + } } - // Process the transactions in chronologically ascending order - while(!txq.empty()) { - auto& tx_info = txq.top(); - cryptonote::transaction tx; - crypto::hash tx_hash; - THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(tx_info, tx, tx_hash), error::wallet_internal_error, "Failed to get transaction from daemon (2)"); - process_new_transaction(tx_hash, tx, tx_info.output_indices, tx_info.block_height, 0, tx_info.block_timestamp, false, tx_info.in_pool, tx_info.double_spend_seen, {}, {}); - txq.pop(); + return tx_entries; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::sort_scan_tx_entries(std::vector<process_tx_entry_t> &unsorted_tx_entries) +{ + // If any txs we're scanning have the same height, then we need to request the + // blocks those txs are in to see what order they appear in the chain. We + // need to scan txs in the same order they appear in the chain so that the + // `m_transfers` container holds entries in a consistently sorted order. + // This ensures that hot wallets <> cold wallets both maintain the same order + // of m_transfers, which they rely on when importing/exporting. Same goes + // for multisig wallets when they synchronize. + std::set<uint64_t> entry_heights; + std::set<uint64_t> entry_heights_requested; + COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request req; + COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response res; + for (const auto & tx_info : unsorted_tx_entries) + { + if (!tx_info.tx_entry.in_pool && !cryptonote::is_coinbase(tx_info.tx)) + { + const uint64_t height = tx_info.tx_entry.block_height; + if (entry_heights.find(height) == entry_heights.end()) + { + entry_heights.insert(height); + } + else if (entry_heights_requested.find(height) == entry_heights_requested.end()) + { + req.heights.push_back(height); + entry_heights_requested.insert(height); + } + } + } + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + bool r = net_utils::invoke_http_bin("/getblocks_by_height.bin", req, res, *m_http_client, rpc_timeout); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to get blocks by height from daemon"); + THROW_WALLET_EXCEPTION_IF(res.blocks.size() != req.heights.size(), error::wallet_internal_error, "Failed to get blocks by height from daemon"); + } + + std::unordered_map<uint64_t, cryptonote::block> parsed_blocks; + for (size_t i = 0; i < res.blocks.size(); ++i) + { + const auto &blk = res.blocks[i]; + cryptonote::block parsed_block; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_block_from_blob(blk.block, parsed_block), + error::wallet_internal_error, "Failed to parse block"); + parsed_blocks[req.heights[i]] = std::move(parsed_block); + } + + // sort tx_entries in chronologically ascending order; pool txs to the back + auto cmp_tx_entry = [&](const process_tx_entry_t& l, const process_tx_entry_t& r) + { + if (l.tx_entry.in_pool) + return false; + else if (r.tx_entry.in_pool) + return true; + else if (l.tx_entry.block_height > r.tx_entry.block_height) + return false; + else if (l.tx_entry.block_height < r.tx_entry.block_height) + return true; + else // l.tx_entry.block_height == r.tx_entry.block_height + { + // coinbase tx is the first tx in a block + if (cryptonote::is_coinbase(l.tx)) + return true; + if (cryptonote::is_coinbase(r.tx)) + return false; + + // see which tx hash comes first in the block + THROW_WALLET_EXCEPTION_IF(parsed_blocks.find(l.tx_entry.block_height) == parsed_blocks.end(), + error::wallet_internal_error, "Expected block not returned by daemon"); + const auto &blk = parsed_blocks[l.tx_entry.block_height]; + for (const auto &tx_hash : blk.tx_hashes) + { + if (tx_hash == l.tx_hash) + return true; + if (tx_hash == r.tx_hash) + return false; + } + THROW_WALLET_EXCEPTION(error::wallet_internal_error, "Tx hashes not found in block"); + return false; + } + }; + std::sort(unsorted_tx_entries.begin(), unsorted_tx_entries.end(), cmp_tx_entry); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_scan_txs(const tx_entry_data &txs_to_scan, const tx_entry_data &txs_to_reprocess, const std::unordered_set<crypto::hash> &tx_hashes_to_reprocess, detached_blockchain_data &dbd) +{ + LOG_PRINT_L0("Processing " << txs_to_scan.tx_entries.size() << " txs, re-processing " + << txs_to_reprocess.tx_entries.size() << " txs"); + + // Sort the txs in chronologically ascending order they appear in the chain + std::vector<process_tx_entry_t> process_txs; + process_txs.reserve(txs_to_scan.tx_entries.size() + txs_to_reprocess.tx_entries.size()); + process_txs.insert(process_txs.end(), txs_to_scan.tx_entries.begin(), txs_to_scan.tx_entries.end()); + process_txs.insert(process_txs.end(), txs_to_reprocess.tx_entries.begin(), txs_to_reprocess.tx_entries.end()); + sort_scan_tx_entries(process_txs); + + for (const auto &tx_info : process_txs) + { + const auto &tx_entry = tx_info.tx_entry; + + // Ignore callbacks when re-processing a tx to avoid confusing feedback to user + bool ignore_callbacks = tx_hashes_to_reprocess.find(tx_info.tx_hash) != tx_hashes_to_reprocess.end(); + process_new_transaction( + tx_info.tx_hash, + tx_info.tx, + tx_entry.output_indices, + tx_entry.block_height, + 0, + tx_entry.block_timestamp, + cryptonote::is_coinbase(tx_info.tx), + tx_entry.in_pool, + tx_entry.double_spend_seen, + {}, {}, // unused caches + ignore_callbacks); + + // Re-set destination addresses if they were previously set + if (m_confirmed_txs.find(tx_info.tx_hash) != m_confirmed_txs.end() && + dbd.detached_confirmed_txs_dests.find(tx_info.tx_hash) != dbd.detached_confirmed_txs_dests.end()) + { + m_confirmed_txs[tx_info.tx_hash].m_dests = std::move(dbd.detached_confirmed_txs_dests[tx_info.tx_hash]); + } + } + + LOG_PRINT_L0("Done processing " << txs_to_scan.tx_entries.size() << " txs and re-processing " + << txs_to_reprocess.tx_entries.size() << " txs"); +} +//---------------------------------------------------------------------------------------------------- +void reattach_blockchain(hashchain &blockchain, wallet2::detached_blockchain_data &dbd) +{ + if (!dbd.detached_blockchain.empty()) + { + LOG_PRINT_L0("Re-attaching " << dbd.detached_blockchain.size() << " blocks"); + for (size_t i = 0; i < dbd.detached_blockchain.size(); ++i) + blockchain.push_back(dbd.detached_blockchain[i]); + } + + THROW_WALLET_EXCEPTION_IF(blockchain.size() != dbd.original_chain_size, + error::wallet_internal_error, "Unexpected blockchain size after re-attaching"); +} +//---------------------------------------------------------------------------------------------------- +bool has_nonrequested_tx_at_height_or_above_requested(uint64_t height, const std::unordered_set<crypto::hash> &requested_txids, const wallet2::transfer_container &transfers, + const wallet2::payment_container &payments, const serializable_unordered_map<crypto::hash, wallet2::confirmed_transfer_details> &confirmed_txs) +{ + for (const auto &td : transfers) + if (td.m_block_height >= height && requested_txids.find(td.m_txid) == requested_txids.end()) + return true; + + for (const auto &pmt : payments) + if (pmt.second.m_block_height >= height && requested_txids.find(pmt.second.m_tx_hash) == requested_txids.end()) + return true; + + for (const auto &ct : confirmed_txs) + if (ct.second.m_block_height >= height && requested_txids.find(ct.first) == requested_txids.end()) + return true; + + return false; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::scan_tx(const std::unordered_set<crypto::hash> &txids) +{ + // Get the transactions from daemon in batches sorted lowest height to highest + tx_entry_data txs_to_scan = get_tx_entries(txids); + if (txs_to_scan.tx_entries.empty()) + return; + + // Re-process wallet's txs >= lowest scan_tx height. Re-processing ensures + // process_new_transaction is called with txs in chronological order. Say that + // tx2 spends an output from tx1, and the user calls scan_tx(tx1) *after* tx2 + // has already been scanned. In this case, we will "re-process" tx2 *after* + // processing tx1 to ensure the wallet picks up that tx2 spends the output + // from tx1, and to ensure transfers are placed in the sorted transfers + // container in chronological order. Note: in the above example, if tx2 is + // a sweep to a different wallet's address, the wallet will not be able to + // detect tx2. The wallet would need to scan tx1 first in that case. + // TODO: handle this sweep case + detached_blockchain_data dbd; + dbd.original_chain_size = m_blockchain.size(); + if (m_blockchain.size() > txs_to_scan.lowest_height) + { + // When connected to an untrusted daemon, if we will need to re-process 1+ + // tx that the user did not request to scan, then we fail out because + // re-requesting those unexpected txs from the daemon poses a more severe + // and unintuitive privacy risk to the user + THROW_WALLET_EXCEPTION_IF(!is_trusted_daemon() && + has_nonrequested_tx_at_height_or_above_requested(txs_to_scan.lowest_height, txids, m_transfers, m_payments, m_confirmed_txs), + error::wont_reprocess_recent_txs_via_untrusted_daemon + ); + + LOG_PRINT_L0("Re-processing wallet's existing txs (if any) starting from height " << txs_to_scan.lowest_height); + dbd = detach_blockchain(txs_to_scan.lowest_height); + } + std::unordered_set<crypto::hash> tx_hashes_to_reprocess; + tx_hashes_to_reprocess.reserve(dbd.detached_tx_hashes.size()); + for (const auto &tx_hash : dbd.detached_tx_hashes) + { + if (txids.find(tx_hash) == txids.end()) + tx_hashes_to_reprocess.insert(tx_hash); + } + // re-request txs from daemon to re-process with all tx data needed + tx_entry_data txs_to_reprocess = get_tx_entries(tx_hashes_to_reprocess); + + process_scan_txs(txs_to_scan, txs_to_reprocess, tx_hashes_to_reprocess, dbd); + reattach_blockchain(m_blockchain, dbd); + + // If the highest scan_tx height exceeds the wallet's known scan height, then + // the wallet should skip ahead to the scan_tx's height in order to service + // the request in a timely manner. Skipping unrequested transactions avoids + // generating sequences of calls to process_new_transaction which process + // transactions out-of-order, relative to their order in the blockchain, as + // the process_new_transaction implementation requires transactions to be + // processed in blockchain order. If a user misses a tx, they should either + // use rescan_bc, or manually scan missed txs with scan_tx. + uint64_t skip_to_height = txs_to_scan.highest_height + 1; + if (skip_to_height > m_blockchain.size()) + { + m_skip_to_height = skip_to_height; + LOG_PRINT_L0("Skipping refresh to height " << skip_to_height); + + // update last block reward here because the refresh loop won't necessarily set it + try + { + cryptonote::block_header_response block_header; + if (m_node_rpc_proxy.get_block_header_by_height(txs_to_scan.highest_height, block_header)) + throw std::runtime_error("Failed to request block header by height"); + m_last_block_reward = block_header.reward; + } + catch (...) { MERROR("Failed getting block header at height " << txs_to_scan.highest_height); } + + // TODO: use fast_refresh instead of refresh to update m_blockchain. It needs refactoring to work correctly here. + // Or don't refresh at all, and let it update on the next refresh loop. + refresh(is_trusted_daemon()); } } //---------------------------------------------------------------------------------------------------- @@ -1953,7 +2186,7 @@ bool wallet2::spends_one_of_ours(const cryptonote::transaction &tx) const return false; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache) +void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache, bool ignore_callbacks) { PERF_TIMER(process_new_transaction); // In this function, tx (probably) only contains the base information @@ -1995,7 +2228,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote if (pk_index > 1) break; LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << txid); - if(0 != m_callback) + if(!ignore_callbacks && 0 != m_callback) m_callback->on_skip_transaction(height, txid, tx); break; } @@ -2208,7 +2441,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); } LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); - if (0 != m_callback) + if (!ignore_callbacks && 0 != m_callback) m_callback->on_money_received(height, txid, tx, td.m_amount, 0, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); } total_received_1 += amount; @@ -2286,7 +2519,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); - if (0 != m_callback) + if (!ignore_callbacks && 0 != m_callback) m_callback->on_money_received(height, txid, tx, td.m_amount, burnt, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); } total_received_1 += extra_amount; @@ -2340,7 +2573,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote { LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid); set_spent(it->second, height); - if (0 != m_callback) + if (!ignore_callbacks && 0 != m_callback) m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index); } } @@ -2575,7 +2808,7 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans bool wallet2::should_skip_block(const cryptonote::block &b, uint64_t height) const { // seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup - return !(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height); + return !(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height && height >= m_skip_to_height); } //---------------------------------------------------------------------------------------------------- void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache) @@ -2654,7 +2887,83 @@ void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::bl error = !cryptonote::parse_and_validate_block_from_blob(blob, bl, bl_id); } //---------------------------------------------------------------------------------------------------- -void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t ¤t_height) +void read_pool_txs(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request &req, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response &res, bool r, const std::vector<crypto::hash> &txids, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs) +{ + if (r && res.status == CORE_RPC_STATUS_OK) + { + MDEBUG("Reading pool txs"); + if (res.txs.size() == req.txs_hashes.size()) + { + for (const auto &tx_entry: res.txs) + { + if (tx_entry.in_pool) + { + cryptonote::transaction tx; + cryptonote::blobdata bd; + crypto::hash tx_hash; + + if (get_pruned_tx(tx_entry, tx, tx_hash)) + { + const std::vector<crypto::hash>::const_iterator i = std::find_if(txids.begin(), txids.end(), + [tx_hash](const crypto::hash &e) { return e == tx_hash; }); + if (i != txids.end()) + { + txs.push_back(std::make_tuple(tx, tx_hash, tx_entry.double_spend_seen)); + } + else + { + MERROR("Got txid " << tx_hash << " which we did not ask for"); + } + } + else + { + LOG_PRINT_L0("Failed to parse transaction from daemon"); + } + } + else + { + LOG_PRINT_L1("Transaction from daemon was in pool, but is no more"); + } + } + } + else + { + LOG_PRINT_L0("Expected " << req.txs_hashes.size() << " out of " << txids.size() << " tx(es), got " << res.txs.size()); + } + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_pool_info_extent(const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response &res, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed) +{ + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> added_pool_txs; + added_pool_txs.reserve(res.added_pool_txs.size() + res.remaining_added_pool_txids.size()); + + for (const auto &pool_tx: res.added_pool_txs) + { + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_base_from_blob(pool_tx.tx_blob, tx), + error::wallet_internal_error, "Failed to validate transaction base from daemon"); + added_pool_txs.push_back(std::make_tuple(tx, pool_tx.tx_hash, pool_tx.double_spend_seen)); + } + + // getblocks.bin may return more added pool transactions than we're allowed to request in restricted mode + if (!res.remaining_added_pool_txids.empty()) + { + // request the remaining txs + m_node_rpc_proxy.get_transactions(res.remaining_added_pool_txids, + [this, &res, &added_pool_txs](const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request &req_t, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response &resp_t, bool r) + { + read_pool_txs(req_t, resp_t, r, res.remaining_added_pool_txids, added_pool_txs); + if (!r || resp_t.status != CORE_RPC_STATUS_OK) + LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << get_rpc_status(resp_t.status)); + } + ); + } + + update_pool_state_from_pool_data(res.pool_info_extent == COMMAND_RPC_GET_BLOCKS_FAST::INCREMENTAL, res.removed_pool_txids, added_pool_txs, process_txs, refreshed); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t ¤t_height, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>>& process_pool_txs) { cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res); @@ -2666,25 +2975,45 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, req.start_height = start_height; req.no_miner_tx = m_refresh_type == RefreshNoCoinbase; + req.requested_info = first ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY; + if (try_incremental) + req.pool_info_since = m_pool_info_query_time; + { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); bool r = net_utils::invoke_http_bin("/getblocks.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "getblocks.bin", error::get_blocks_error, get_rpc_status(res.status)); THROW_WALLET_EXCEPTION_IF(res.blocks.size() != res.output_indices.size(), error::wallet_internal_error, "mismatched blocks (" + boost::lexical_cast<std::string>(res.blocks.size()) + ") and output_indices (" + boost::lexical_cast<std::string>(res.output_indices.size()) + ") sizes from daemon"); - check_rpc_cost("/getblocks.bin", res.credits, pre_call_credits, 1 + res.blocks.size() * COST_PER_BLOCK); } blocks_start_height = res.start_height; blocks = std::move(res.blocks); o_indices = std::move(res.output_indices); current_height = res.current_height; + if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE) + m_pool_info_query_time = res.daemon_time; MDEBUG("Pulled blocks: blocks_start_height " << blocks_start_height << ", count " << blocks.size() - << ", height " << blocks_start_height + blocks.size() << ", node height " << res.current_height); + << ", height " << blocks_start_height + blocks.size() << ", node height " << res.current_height + << ", pool info " << static_cast<unsigned int>(res.pool_info_extent)); + + if (first) + { + if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE) + { + process_pool_info_extent(res, process_pool_txs, true); + } + else + { + // If we did not get any pool info, neither incremental nor the whole pool, we probably talk + // to a daemon that does not yet support giving back pool info with the 'getblocks' call, + // and we have to update in the "old way" + update_pool_state_by_pool_query(process_pool_txs, true); + } + } + } //---------------------------------------------------------------------------------------------------- void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes) @@ -2697,11 +3026,8 @@ void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - req.client = get_client_signature(); - uint64_t pre_call_credits = m_rpc_payment_state.credits; bool r = net_utils::invoke_http_bin("/gethashes.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "gethashes.bin", error::get_hashes_error, get_rpc_status(res.status)); - check_rpc_cost("/gethashes.bin", res.credits, pre_call_credits, 1 + res.m_block_ids.size() * COST_PER_BLOCK_HASH); } blocks_start_height = res.start_height; @@ -2716,7 +3042,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry THROW_WALLET_EXCEPTION_IF(blocks.size() != parsed_blocks.size(), error::wallet_internal_error, "size mismatch"); THROW_WALLET_EXCEPTION_IF(!m_blockchain.is_in_bounds(current_index), error::out_of_hashchain_bounds_error); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); size_t num_txes = 0; @@ -2890,7 +3216,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry tr("reorg exceeds maximum allowed depth, use 'set max-reorg-depth N' to allow it, reorg depth: ") + std::to_string(reorg_depth)); - detach_blockchain(current_index, output_tracker_cache); + handle_reorg(current_index, output_tracker_cache); process_new_blockchain_entry(bl, blocks[i], parsed_blocks[i], bl_id, current_index, tx_cache_data, tx_cache_data_offset, output_tracker_cache); } else @@ -2914,7 +3240,32 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo refresh(trusted_daemon, start_height, blocks_fetched, received_money); } //---------------------------------------------------------------------------------------------------- -void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception) +void check_block_hard_fork_version(cryptonote::network_type nettype, uint8_t hf_version, uint64_t height, bool &wallet_is_outdated, bool &daemon_is_outdated) +{ + const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks + : nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks; + const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks + : nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks; + + wallet_is_outdated = hf_version > wallet_hard_forks[wallet_num_hard_forks-1].version; + if (wallet_is_outdated) + return; + + // check block's height falls within wallet's expected range for block's given version + size_t fork_index; + for (fork_index = 0; fork_index < wallet_num_hard_forks; ++fork_index) + if (wallet_hard_forks[fork_index].version == hf_version) + break; + THROW_WALLET_EXCEPTION_IF(fork_index == wallet_num_hard_forks, error::wallet_internal_error, "Fork not found in table"); + uint64_t start_height = wallet_hard_forks[fork_index].height; + uint64_t end_height = fork_index == wallet_num_hard_forks - 1 + ? std::numeric_limits<uint64_t>::max() + : wallet_hard_forks[fork_index + 1].height; + + daemon_is_outdated = height < start_height || height >= end_height; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::pull_and_parse_next_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>>& process_pool_txs, bool &last, bool &error, std::exception_ptr &exception) { error = false; last = false; @@ -2936,10 +3287,10 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks // pull the new blocks std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> o_indices; uint64_t current_height; - pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices, current_height); + pull_blocks(first, try_incremental, start_height, blocks_start_height, short_chain_history, blocks, o_indices, current_height, process_pool_txs); THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "Mismatched sizes of blocks and o_indices"); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); parsed_blocks.resize(blocks.size()); for (size_t i = 0; i < blocks.size(); ++i) @@ -2955,6 +3306,23 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks error = true; break; } + + if (!m_allow_mismatched_daemon_version) + { + // make sure block's hard fork version is expected at the block's height + uint8_t hf_version = parsed_blocks[i].block.major_version; + uint64_t height = blocks_start_height + i; + bool wallet_is_outdated = false; + bool daemon_is_outdated = false; + check_block_hard_fork_version(m_nettype, hf_version, height, wallet_is_outdated, daemon_is_outdated); + THROW_WALLET_EXCEPTION_IF(wallet_is_outdated || daemon_is_outdated, error::incorrect_fork_version, + "Unexpected hard fork version v" + std::to_string(hf_version) + " at height " + std::to_string(height) + ". " + + (wallet_is_outdated + ? "Make sure your wallet is up to date" + : "Make sure the node you are connected to is running the latest version") + ); + } + parsed_blocks[i].o_indices = std::move(o_indices[i]); } @@ -2983,9 +3351,10 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks } } -void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes) +void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes, bool remove_if_found) { - // remove pool txes to us that aren't in the pool anymore + // remove pool txes to us that aren't in the pool anymore (remove_if_found = false), + // or remove pool txes to us that were reported as removed (remove_if_found = true) std::unordered_multimap<crypto::hash, wallet2::pool_payment_details>::iterator uit = m_unconfirmed_payments.begin(); while (uit != m_unconfirmed_payments.end()) { @@ -3000,9 +3369,9 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe } } auto pit = uit++; - if (!found) + if ((!remove_if_found && !found) || (remove_if_found && found)) { - MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool"); + MDEBUG("Removing " << txid << " from unconfirmed payments"); m_unconfirmed_payments.erase(pit); if (0 != m_callback) m_callback->on_pool_tx_removed(txid); @@ -3011,9 +3380,179 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe } //---------------------------------------------------------------------------------------------------- -void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed) +// Code that is common to 'update_pool_state_by_pool_query' and 'update_pool_state_from_pool_data': +// Check wether a tx in the pool is worthy of processing because we did not see it +// yet or because it is "interesting" out of special circumstances +bool wallet2::accept_pool_tx_for_processing(const crypto::hash &txid) +{ + bool txid_found_in_up = false; + for (const auto &up: m_unconfirmed_payments) + { + if (up.second.m_pd.m_tx_hash == txid) + { + txid_found_in_up = true; + break; + } + } + if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) + { + // if it's for us, we want to keep track of whether we saw a double spend, so don't bail out + if (!txid_found_in_up) + { + LOG_PRINT_L2("Already seen " << txid << ", and not for us, skipped"); + return false; + } + } + if (!txid_found_in_up) + { + LOG_PRINT_L1("Found new pool tx: " << txid); + bool found = false; + for (const auto &i: m_unconfirmed_txs) + { + if (i.first == txid) + { + found = true; + // if this is a payment to yourself at a different subaddress account, don't skip it + // so that you can see the incoming pool tx with 'show_transfers' on that receiving subaddress account + const unconfirmed_transfer_details& utd = i.second; + for (const auto& dst : utd.m_dests) + { + auto subaddr_index = m_subaddresses.find(dst.addr.m_spend_public_key); + if (subaddr_index != m_subaddresses.end() && subaddr_index->second.major != utd.m_subaddr_account) + { + found = false; + break; + } + } + break; + } + } + if (!found) + { + // not one of those we sent ourselves + return true; + } + else + { + LOG_PRINT_L1("We sent that one"); + return false; + } + } + else { + return false; + } +} +//---------------------------------------------------------------------------------------------------- +// Code that is common to 'update_pool_state_by_pool_query' and 'update_pool_state_from_pool_data': +// Process an unconfirmed transfer after we know whether it's in the pool or not +void wallet2::process_unconfirmed_transfer(bool incremental, const crypto::hash &txid, wallet2::unconfirmed_transfer_details &tx_details, bool seen_in_pool, std::chrono::system_clock::time_point now, bool refreshed) +{ + // TODO: set tx_propagation_timeout to CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE * 3 / 2 after v15 hardfork + constexpr const std::chrono::seconds tx_propagation_timeout{500}; + if (seen_in_pool) + { + if (tx_details.m_state != wallet2::unconfirmed_transfer_details::pending_in_pool) + { + tx_details.m_state = wallet2::unconfirmed_transfer_details::pending_in_pool; + MINFO("Pending txid " << txid << " seen in pool, marking as pending in pool"); + } + } + else + { + if (!incremental) + { + if (tx_details.m_state == wallet2::unconfirmed_transfer_details::pending_in_pool) + { + // For the probably unlikely case that a tx once seen in the pool vanishes + // again set back to 'pending' + tx_details.m_state = wallet2::unconfirmed_transfer_details::pending; + MINFO("Already seen txid " << txid << " vanished from pool, marking as pending"); + } + } + // If a tx is pending for a "long time" without appearing in the pool, and if + // we have refreshed and thus had a chance to really see it if it was there, + // judge it as failed; the waiting for timeout and refresh happened avoids + // false alarms with txs going to 'failed' too early + if (tx_details.m_state == wallet2::unconfirmed_transfer_details::pending && refreshed && + now > std::chrono::system_clock::from_time_t(tx_details.m_sent_time) + tx_propagation_timeout) + { + LOG_PRINT_L1("Pending txid " << txid << " not in pool after " << tx_propagation_timeout.count() << + " seconds, marking as failed"); + tx_details.m_state = wallet2::unconfirmed_transfer_details::failed; + + // the inputs aren't spent anymore, since the tx failed + for (size_t vini = 0; vini < tx_details.m_tx.vin.size(); ++vini) + { + if (tx_details.m_tx.vin[vini].type() == typeid(txin_to_key)) + { + txin_to_key &tx_in_to_key = boost::get<txin_to_key>(tx_details.m_tx.vin[vini]); + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[i]; + if (td.m_key_image == tx_in_to_key.k_image) + { + LOG_PRINT_L1("Resetting spent status for output " << vini << ": " << td.m_key_image); + set_unspent(i); + break; + } + } + } + } + } + } +} +//---------------------------------------------------------------------------------------------------- +// This public method is typically called to make sure that the wallet's pool state is up-to-date by +// clients like simplewallet and the RPC daemon. Before incremental update this was the same method +// that 'refresh' also used, but now it's more complicated because for the time being we support +// the "old" and the "new" way of updating the pool and because only the 'getblocks' call supports +// incremental update but we don't want any blocks here. +// +// simplewallet does NOT update the pool info during automatic refresh to avoid disturbing interactive +// messages and prompts. When it finally calls this method here "to catch up" so to say we can't use +// incremental update anymore, because with that we might miss some txs altogether. +void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed, bool try_incremental) { - MTRACE("update_pool_state start"); + bool updated = false; + if (m_pool_info_query_time != 0 && try_incremental) + { + // We are connected to a daemon that supports giving back pool data with the 'getblocks' call, + // thus use that, to get the chance to work incrementally and to keep working incrementally; + // 'POOL_ONLY' was created to support this case + cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res); + + req.requested_info = COMMAND_RPC_GET_BLOCKS_FAST::POOL_ONLY; + req.pool_info_since = m_pool_info_query_time; + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + bool r = net_utils::invoke_http_bin("/getblocks.bin", req, res, *m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "getblocks.bin", error::get_blocks_error, get_rpc_status(res.status)); + } + + m_pool_info_query_time = res.daemon_time; + if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE) + { + process_pool_info_extent(res, process_txs, refreshed); + updated = true; + } + // We SHOULD get pool data here, but if for some crazy reason we don't fall back to the "old" method + } + if (!updated) + { + update_pool_state_by_pool_query(process_txs, refreshed); + } +} +//---------------------------------------------------------------------------------------------------- +// This is the "old" way of updating the pool with separate queries to get the pool content, used before +// the 'getblocks' command was able to give back pool data in addition to blocks. Before this code was +// the public 'update_pool_state' method. The logic is unchanged. This is a candidate for elimination +// when it's sure that no more "old" daemons can be possibly around. +void wallet2::update_pool_state_by_pool_query(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed) +{ + MTRACE("update_pool_state_by_pool_query start"); + process_txs.clear(); auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() { m_encrypt_keys_after_refresh.reset(); @@ -3025,22 +3564,18 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); bool r = epee::net_utils::invoke_http_json("/get_transaction_pool_hashes.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_transaction_pool_hashes.bin", error::get_tx_pool_error); - check_rpc_cost("/get_transaction_pool_hashes.bin", res.credits, pre_call_credits, 1 + res.tx_hashes.size() * COST_PER_POOL_HASH); } - MTRACE("update_pool_state got pool"); + MTRACE("update_pool_state_by_pool_query got pool"); // remove any pending tx that's not in the pool - // TODO: set tx_propagation_timeout to CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE * 3 / 2 after v15 hardfork - constexpr const std::chrono::seconds tx_propagation_timeout{500}; const auto now = std::chrono::system_clock::now(); std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin(); while (it != m_unconfirmed_txs.end()) { const crypto::hash &txid = it->first; + MDEBUG("Checking m_unconfirmed_txs entry " << txid); bool found = false; for (const auto &it2: res.tx_hashes) { @@ -3051,193 +3586,115 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, } } auto pit = it++; - if (!found) - { - // we want to avoid a false positive when we ask for the pool just after - // a tx is removed from the pool due to being found in a new block, but - // just before the block is visible by refresh. So we keep a boolean, so - // that the first time we don't see the tx, we set that boolean, and only - // delete it the second time it is checked (but only when refreshed, so - // we're sure we've seen the blockchain state first) - if (pit->second.m_state == wallet2::unconfirmed_transfer_details::pending) - { - LOG_PRINT_L1("Pending txid " << txid << " not in pool, marking as not in pool"); - pit->second.m_state = wallet2::unconfirmed_transfer_details::pending_not_in_pool; - } - else if (pit->second.m_state == wallet2::unconfirmed_transfer_details::pending_not_in_pool && refreshed && - now > std::chrono::system_clock::from_time_t(pit->second.m_sent_time) + tx_propagation_timeout) - { - LOG_PRINT_L1("Pending txid " << txid << " not in pool after " << tx_propagation_timeout.count() << - " seconds, marking as failed"); - pit->second.m_state = wallet2::unconfirmed_transfer_details::failed; - - // the inputs aren't spent anymore, since the tx failed - for (size_t vini = 0; vini < pit->second.m_tx.vin.size(); ++vini) - { - if (pit->second.m_tx.vin[vini].type() == typeid(txin_to_key)) - { - txin_to_key &tx_in_to_key = boost::get<txin_to_key>(pit->second.m_tx.vin[vini]); - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details &td = m_transfers[i]; - if (td.m_key_image == tx_in_to_key.k_image) - { - LOG_PRINT_L1("Resetting spent status for output " << vini << ": " << td.m_key_image); - set_unspent(i); - break; - } - } - } - } - } - } + process_unconfirmed_transfer(false, txid, pit->second, found, now, refreshed); + MDEBUG("New state of that entry: " << pit->second.m_state); } - MTRACE("update_pool_state done first loop"); + MTRACE("update_pool_state_by_pool_query done first loop"); // remove pool txes to us that aren't in the pool anymore // but only if we just refreshed, so that the tx can go in // the in transfers list instead (or nowhere if it just // disappeared without being mined) if (refreshed) - remove_obsolete_pool_txs(res.tx_hashes); + remove_obsolete_pool_txs(res.tx_hashes, false); - MTRACE("update_pool_state done second loop"); + MTRACE("update_pool_state_by_pool_query done second loop"); // gather txids of new pool txes to us - std::vector<std::pair<crypto::hash, bool>> txids; + std::vector<crypto::hash> txids; for (const auto &txid: res.tx_hashes) { - bool txid_found_in_up = false; - for (const auto &up: m_unconfirmed_payments) + if (accept_pool_tx_for_processing(txid)) + txids.push_back(txid); + } + + m_node_rpc_proxy.get_transactions(txids, + [this, &txids, &process_txs](const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request &req_t, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response &resp_t, bool r) { - if (up.second.m_pd.m_tx_hash == txid) - { - txid_found_in_up = true; - break; - } + read_pool_txs(req_t, resp_t, r, txids, process_txs); + if (!r || resp_t.status != CORE_RPC_STATUS_OK) + LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << get_rpc_status(resp_t.status)); } - if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) + ); + + MTRACE("update_pool_state_by_pool_query end"); +} +//---------------------------------------------------------------------------------------------------- +// Update pool state from pool data we got together with block data, either incremental data with +// txs that are new in the pool since the last time we queried and the ids of txs that were +// removed from the pool since then, or the whole content of the pool if incremental was not +// possible, e.g. because the server was just started or restarted. +void wallet2::update_pool_state_from_pool_data(bool incremental, const std::vector<crypto::hash> &removed_pool_txids, const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &added_pool_txs, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed) +{ + MTRACE("update_pool_state_from_pool_data start"); + auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() { + m_encrypt_keys_after_refresh.reset(); + }); + + if (refreshed) + { + if (incremental) { - // if it's for us, we want to keep track of whether we saw a double spend, so don't bail out - if (!txid_found_in_up) - { - LOG_PRINT_L2("Already seen " << txid << ", and not for us, skipped"); - continue; - } + // Delete from the list of unconfirmed payments what the daemon reported as tx that was removed from + // pool; do so only after refresh to not delete too early and too eagerly; maybe we will find the tx + // later in a block, or not, or find it again in the pool txs because it was first removed but then + // somehow quickly "resurrected" - that all does not matter here, we retrace the removal + remove_obsolete_pool_txs(removed_pool_txids, true); } - if (!txid_found_in_up) + else { - LOG_PRINT_L1("Found new pool tx: " << txid); - bool found = false; - for (const auto &i: m_unconfirmed_txs) + // Delete from the list of unconfirmed payments what we don't find anymore in the pool; a bit + // unfortunate that we have to build a new vector with ids first, but better than copying and + // modifying the code of 'remove_obsolete_pool_txs' here + std::vector<crypto::hash> txids; + txids.reserve(added_pool_txs.size()); + for (const auto &pool_tx: added_pool_txs) { - if (i.first == txid) - { - found = true; - // if this is a payment to yourself at a different subaddress account, don't skip it - // so that you can see the incoming pool tx with 'show_transfers' on that receiving subaddress account - const unconfirmed_transfer_details& utd = i.second; - for (const auto& dst : utd.m_dests) - { - auto subaddr_index = m_subaddresses.find(dst.addr.m_spend_public_key); - if (subaddr_index != m_subaddresses.end() && subaddr_index->second.major != utd.m_subaddr_account) - { - found = false; - break; - } - } - break; - } - } - if (!found) - { - // not one of those we sent ourselves - txids.push_back({txid, false}); - } - else - { - LOG_PRINT_L1("We sent that one"); + txids.push_back(std::get<1>(pool_tx)); } + remove_obsolete_pool_txs(txids, false); } } - // get_transaction_pool_hashes.bin may return more transactions than we're allowed to request in restricted mode - const size_t SLICE_SIZE = 100; // RESTRICTED_TRANSACTIONS_COUNT as defined in rpc/core_rpc_server.cpp - for (size_t offset = 0; offset < txids.size(); offset += SLICE_SIZE) + // Possibly remove any pending tx that's not in the pool + const auto now = std::chrono::system_clock::now(); + std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin(); + while (it != m_unconfirmed_txs.end()) { - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - - const size_t n_txids = std::min<size_t>(SLICE_SIZE, txids.size() - offset); - for (size_t n = offset; n < (offset + n_txids); ++n) { - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids.at(n).first)); - } - MDEBUG("asking for " << req.txs_hashes.size() << " transactions"); - req.decode_as_json = false; - req.prune = true; - - bool r; - { - const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); - r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); - if (r && res.status == CORE_RPC_STATUS_OK) - check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX); - } - - MDEBUG("Got " << r << " and " << res.status); - if (r && res.status == CORE_RPC_STATUS_OK) + const crypto::hash &txid = it->first; + MDEBUG("Checking m_unconfirmed_txs entry " << txid); + bool found = false; + for (const auto &pool_tx: added_pool_txs) { - if (res.txs.size() == req.txs_hashes.size()) + if (std::get<1>(pool_tx) == txid) { - for (const auto &tx_entry: res.txs) - { - if (tx_entry.in_pool) - { - cryptonote::transaction tx; - cryptonote::blobdata bd; - crypto::hash tx_hash; - - if (get_pruned_tx(tx_entry, tx, tx_hash)) - { - const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(), - [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; }); - if (i != txids.end()) - { - process_txs.push_back(std::make_tuple(tx, tx_hash, tx_entry.double_spend_seen)); - } - else - { - MERROR("Got txid " << tx_hash << " which we did not ask for"); - } - } - else - { - LOG_PRINT_L0("Failed to parse transaction from daemon"); - } - } - else - { - LOG_PRINT_L1("Transaction from daemon was in pool, but is no more"); - } - } - } - else - { - LOG_PRINT_L0("Expected " << n_txids << " out of " << txids.size() << " tx(es), got " << res.txs.size()); + found = true; + break; } } - else + auto pit = it++; + process_unconfirmed_transfer(incremental, txid, pit->second, found, now, refreshed); + MDEBUG("Resulting state of that entry: " << pit->second.m_state); + } + + // Collect all pool txs that are "interesting" i.e. mostly those that we don't know about yet; + // if we work incrementally and thus see only new pool txs since last time we asked it should + // be rare that we know already about one of those, but check nevertheless + process_txs.clear(); + for (const auto &pool_tx: added_pool_txs) + { + if (accept_pool_tx_for_processing(std::get<1>(pool_tx))) { - LOG_PRINT_L0("Error calling gettransactions daemon RPC: r " << r << ", status " << get_rpc_status(res.status)); + process_txs.push_back(pool_tx); } } - MTRACE("update_pool_state end"); + + MTRACE("update_pool_state_from_pool_data end"); } //---------------------------------------------------------------------------------------------------- void wallet2::process_pool_state(const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs) { + MTRACE("process_pool_state start"); const time_t now = time(NULL); for (const auto &e: txs) { @@ -3252,6 +3709,7 @@ void wallet2::process_pool_state(const std::vector<std::tuple<cryptonote::transa m_scanned_pool_txs[0].clear(); } } + MTRACE("process_pool_state end"); } //---------------------------------------------------------------------------------------------------- void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force) @@ -3372,7 +3830,7 @@ std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> wallet2::create return cache; } //---------------------------------------------------------------------------------------------------- -void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool) +void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool, bool try_incremental, uint64_t max_blocks) { if (m_offline) { @@ -3381,49 +3839,21 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo return; } - if(m_light_wallet) { - - // MyMonero get_address_info needs to be called occasionally to trigger wallet sync. - // This call is not really needed for other purposes and can be removed if mymonero changes their backend. - tools::COMMAND_RPC_GET_ADDRESS_INFO::response res; - - // Get basic info - if(light_wallet_get_address_info(res)) { - // Last stored block height - uint64_t prev_height = m_light_wallet_blockchain_height; - // Update lw heights - m_light_wallet_scanned_block_height = res.scanned_block_height; - m_light_wallet_blockchain_height = res.blockchain_height; - // If new height - call new_block callback - if(m_light_wallet_blockchain_height != prev_height) - { - MDEBUG("new block since last time!"); - m_callback->on_lw_new_block(m_light_wallet_blockchain_height - 1); - } - m_light_wallet_connected = true; - MDEBUG("lw scanned block height: " << m_light_wallet_scanned_block_height); - MDEBUG("lw blockchain height: " << m_light_wallet_blockchain_height); - MDEBUG(m_light_wallet_blockchain_height-m_light_wallet_scanned_block_height << " blocks behind"); - // TODO: add wallet created block info - - light_wallet_get_address_txs(); - } else - m_light_wallet_connected = false; - - // Lighwallet refresh done - return; - } received_money = false; blocks_fetched = 0; uint64_t added_blocks = 0; size_t try_count = 0; crypto::hash last_tx_hash_id = m_transfers.size() ? m_transfers.back().m_txid : null_hash; std::list<crypto::hash> short_chain_history; - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); uint64_t blocks_start_height; std::vector<cryptonote::block_complete_entry> blocks; std::vector<parsed_block> parsed_blocks; + // TODO moneromooo-monero says this about the "refreshed" variable: + // "I had to reorder some code to fix... a timing info leak IIRC. In turn, this undid something I had fixed before, ... a subtle race condition with the txpool. + // It was pretty subtle IIRC, and so I needed time to think about how to refix it after the move, and I never got to it." + // https://github.com/monero-project/monero/pull/6097 bool refreshed = false; std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> output_tracker_cache; hw::device &hwdev = m_account.get_device(); @@ -3431,9 +3861,9 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo // pull the first set of blocks get_short_chain_history(short_chain_history, (m_first_refresh_done || trusted_daemon) ? 1 : FIRST_REFRESH_GRANULARITY); m_run.store(true, std::memory_order_relaxed); - if (start_height > m_blockchain.size() || m_refresh_from_block_height > m_blockchain.size()) { + if (start_height > m_blockchain.size() || m_refresh_from_block_height > m_blockchain.size() || m_skip_to_height > m_blockchain.size()) { if (!start_height) - start_height = m_refresh_from_block_height; + start_height = std::max(m_refresh_from_block_height, m_skip_to_height);; // we can shortcut by only pulling hashes up to the start_height fast_refresh(start_height, blocks_start_height, short_chain_history); // regenerate the history now that we've got a full set of hashes @@ -3456,15 +3886,18 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo auto scope_exit_handler_hwdev = epee::misc_utils::create_scope_leave_handler([&](){hwdev.computing_key_images(false);}); + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_pool_txs; + // Getting and processing the pool state has moved down into method 'pull_blocks' to + // allow for "conventional" as well as "incremental" update. However the following + // principle of getting all info first (pool AND blocks) and only process txs afterwards + // still holds and is still respected: // get updated pool state first, but do not process those txes just yet, // since that might cause a password prompt, which would introduce a data // leak allowing a passive adversary with traffic analysis capability to // infer when we get an incoming output - std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_pool_txs; - update_pool_state(process_pool_txs, true); bool first = true, last = false; - while(m_run.load(std::memory_order_relaxed)) + while(m_run.load(std::memory_order_relaxed) && blocks_fetched < max_blocks) { uint64_t next_blocks_start_height; std::vector<cryptonote::block_complete_entry> next_blocks; @@ -3482,11 +3915,10 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo if (!first && blocks.empty()) { m_node_rpc_proxy.set_height(m_blockchain.size()); - refreshed = true; break; } if (!last) - tpool.submit(&waiter, [&]{pull_and_parse_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, parsed_blocks, next_blocks, next_parsed_blocks, last, error, exception);}); + tpool.submit(&waiter, [&]{pull_and_parse_next_blocks(first, try_incremental, start_height, next_blocks_start_height, short_chain_history, blocks, parsed_blocks, next_blocks, next_parsed_blocks, process_pool_txs, last, error, exception);}); if (!first) { @@ -3535,10 +3967,11 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo throw std::runtime_error("proxy exception in refresh thread"); } + m_has_ever_refreshed_from_node = true; + if(!first && blocks_start_height == next_blocks_start_height) { m_node_rpc_proxy.set_height(m_blockchain.size()); - refreshed = true; break; } @@ -3560,9 +3993,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); throw; } - catch (const error::payment_required&) + catch (const error::deprecated_rpc_access&) { - // no point in trying again, it'd just eat up credits THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); throw; } @@ -3571,6 +4003,11 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); throw; } + catch (const error::incorrect_fork_version&) + { + THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); + throw; + } catch (const std::exception&) { blocks_fetched += added_blocks; @@ -3642,11 +4079,8 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> try { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); r = net_utils::invoke_http_bin("/get_output_distribution.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/get_output_distribution.bin"); - check_rpc_cost("/get_output_distribution.bin", res.credits, pre_call_credits, COST_PER_OUTPUT_DISTRIBUTION_0); } catch(...) { @@ -3669,15 +4103,10 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> return true; } //---------------------------------------------------------------------------------------------------- -void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache) +wallet2::detached_blockchain_data wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache) { LOG_PRINT_L0("Detaching blockchain on height " << height); - - // size 1 2 3 4 5 6 7 8 9 - // block 0 1 2 3 4 5 6 7 8 - // C - THROW_WALLET_EXCEPTION_IF(height < m_blockchain.offset() && m_blockchain.size() > m_blockchain.offset(), - error::wallet_internal_error, "Daemon claims reorg below last checkpoint"); + detached_blockchain_data dbd; size_t transfers_detached = 0; @@ -3719,16 +4148,32 @@ void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, ui THROW_WALLET_EXCEPTION_IF(it_pk == m_pub_keys.end(), error::wallet_internal_error, "public key not found"); m_pub_keys.erase(it_pk); } + transfers_detached = std::distance(it, m_transfers.end()); + dbd.detached_tx_hashes.reserve(transfers_detached); + for (size_t i = i_start; i!=m_transfers.size();i++) + dbd.detached_tx_hashes.insert(std::move(m_transfers[i].m_txid)); + MDEBUG(transfers_detached << " transfers detached / expected " << dbd.detached_tx_hashes.size()); m_transfers.erase(it, m_transfers.end()); - size_t blocks_detached = m_blockchain.size() - height; - m_blockchain.crop(height); + uint64_t blocks_detached = 0; + dbd.original_chain_size = m_blockchain.size(); + if (height >= m_blockchain.offset()) + { + for (uint64_t i = height; i < m_blockchain.size(); ++i) + dbd.detached_blockchain.push_back(m_blockchain[i]); + blocks_detached = m_blockchain.size() - height; + m_blockchain.crop(height); + MDEBUG(blocks_detached << " blocks detached / expected " << dbd.detached_blockchain.size()); + } for (auto it = m_payments.begin(); it != m_payments.end(); ) { if(height <= it->second.m_block_height) + { + dbd.detached_tx_hashes.insert(it->second.m_tx_hash); it = m_payments.erase(it); + } else ++it; } @@ -3736,12 +4181,30 @@ void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, ui for (auto it = m_confirmed_txs.begin(); it != m_confirmed_txs.end(); ) { if(height <= it->second.m_block_height) + { + dbd.detached_tx_hashes.insert(it->first); + dbd.detached_confirmed_txs_dests[it->first] = std::move(it->second.m_dests); it = m_confirmed_txs.erase(it); + } else ++it; } + if (m_callback) + m_callback->on_reorg(height, blocks_detached, transfers_detached); + LOG_PRINT_L0("Detached blockchain on height " << height << ", transfers detached " << transfers_detached << ", blocks detached " << blocks_detached); + return dbd; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::handle_reorg(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache) +{ + // size 1 2 3 4 5 6 7 8 9 + // block 0 1 2 3 4 5 6 7 8 + // C + THROW_WALLET_EXCEPTION_IF(height < m_blockchain.offset() && m_blockchain.size() > m_blockchain.offset(), + error::wallet_internal_error, "Daemon claims reorg below last checkpoint"); + detach_blockchain(height, output_tracker_cache); } //---------------------------------------------------------------------------------------------------- bool wallet2::deinit() @@ -3773,6 +4236,8 @@ bool wallet2::clear() m_subaddress_labels.clear(); m_multisig_rounds_passed = 0; m_device_last_key_image_sync = 0; + m_pool_info_query_time = 0; + m_skip_to_height = 0; return true; } //---------------------------------------------------------------------------------------------------- @@ -3789,6 +4254,8 @@ void wallet2::clear_soft(bool keep_key_images) m_unconfirmed_payments.clear(); m_scanned_pool_txs[0].clear(); m_scanned_pool_txs[1].clear(); + m_pool_info_query_time = 0; + m_skip_to_height = 0; cryptonote::block b; generate_genesis(b); @@ -3918,6 +4385,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetUint64(m_refresh_from_block_height); json.AddMember("refresh_height", value2, json.GetAllocator()); + value2.SetUint64(m_skip_to_height); + json.AddMember("skip_to_height", value2, json.GetAllocator()); + value2.SetInt(m_confirm_non_default_ring_size ? 1 :0); json.AddMember("confirm_non_default_ring_size", value2, json.GetAllocator()); @@ -4020,13 +4490,16 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: json.AddMember("original_view_secret_key", value, json.GetAllocator()); } - value2.SetInt(m_persistent_rpc_client_id ? 1 : 0); + // This value is serialized for compatibility with wallets which support the pay-to-use RPC system + value2.SetInt(0); json.AddMember("persistent_rpc_client_id", value2, json.GetAllocator()); - value2.SetFloat(m_auto_mine_for_rpc_payment_threshold); + // This value is serialized for compatibility with wallets which support the pay-to-use RPC system + value2.SetFloat(0.0f); json.AddMember("auto_mine_for_rpc_payment", value2, json.GetAllocator()); - value2.SetUint64(m_credits_target); + // This value is serialized for compatibility with wallets which support the pay-to-use RPC system + value2.SetUint64(0); json.AddMember("credits_target", value2, json.GetAllocator()); value2.SetInt(m_enable_multisig ? 1 : 0); @@ -4147,6 +4620,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_auto_refresh = true; m_refresh_type = RefreshType::RefreshDefault; m_refresh_from_block_height = 0; + m_skip_to_height = 0; m_confirm_non_default_ring_size = true; m_ask_password = AskPasswordToDecrypt; cryptonote::set_default_decimal_point(CRYPTONOTE_DISPLAY_DECIMAL_POINT); @@ -4177,10 +4651,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_device_derivation_path = ""; m_key_device_type = hw::device::device_type::SOFTWARE; encrypted_secret_keys = false; - m_persistent_rpc_client_id = false; - m_auto_mine_for_rpc_payment_threshold = -1.0f; - m_credits_target = 0; m_enable_multisig = false; + m_allow_mismatched_daemon_version = false; } else if(json.IsObject()) { @@ -4299,6 +4771,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_height, uint64_t, Uint64, false, 0); m_refresh_from_block_height = field_refresh_height; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, skip_to_height, uint64_t, Uint64, false, 0); + m_skip_to_height = field_skip_to_height; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_non_default_ring_size, int, Int, false, true); m_confirm_non_default_ring_size = field_confirm_non_default_ring_size; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ask_password, AskPasswordType, Int, false, AskPasswordToDecrypt); @@ -4406,13 +4880,6 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_original_keys_available = false; } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, persistent_rpc_client_id, int, Int, false, false); - m_persistent_rpc_client_id = field_persistent_rpc_client_id; - // save as float, load as double, because it can happen you can't load back as float... - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_mine_for_rpc_payment, float, Double, false, FLT_MAX); - m_auto_mine_for_rpc_payment_threshold = field_auto_mine_for_rpc_payment; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, credits_target, uint64_t, Uint64, false, 0); - m_credits_target = field_credits_target; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false); m_enable_multisig = field_enable_multisig; } @@ -4684,7 +5151,8 @@ void wallet2::init_type(hw::device::device_type device_type) } /*! - * \brief Generates a wallet or restores one. + * \brief Generates a wallet or restores one. Assumes the multisig setup + * has already completed for the provided multisig info. * \param wallet_ Name of wallet file * \param password Password of wallet file * \param multisig_data The multisig restore info and keys @@ -4743,11 +5211,6 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& crypto::public_key local_signer; THROW_WALLET_EXCEPTION_IF(!crypto::secret_key_to_public_key(spend_secret_key, local_signer), error::invalid_multisig_seed); THROW_WALLET_EXCEPTION_IF(std::find(multisig_signers.begin(), multisig_signers.end(), local_signer) == multisig_signers.end(), error::invalid_multisig_seed); - rct::key skey = rct::zero(); - for (const auto &msk: multisig_keys) - sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); - THROW_WALLET_EXCEPTION_IF(!(rct::rct2sk(skey) == spend_secret_key), error::invalid_multisig_seed); - memwipe(&skey, sizeof(rct::key)); m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); @@ -4758,6 +5221,8 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig = true; m_multisig_threshold = threshold; m_multisig_signers = multisig_signers; + // wallet is assumed already finalized + m_multisig_rounds_passed = multisig::multisig_setup_rounds_required(m_multisig_signers.size(), m_multisig_threshold); setup_keys(password); create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); @@ -5075,12 +5540,11 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, } //---------------------------------------------------------------------------------------------------- std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password, - const std::vector<std::string> &kex_messages) + const std::vector<std::string> &kex_messages, + const bool force_update_use_with_caution /*= false*/) { bool ready{false}; CHECK_AND_ASSERT_THROW_MES(multisig(&ready), "The wallet is not multisig"); - CHECK_AND_ASSERT_THROW_MES(!ready, "Multisig wallet creation process has already been finished"); - CHECK_AND_ASSERT_THROW_MES(kex_messages.size() > 0, "No key exchange messages passed in."); // decrypt account keys epee::misc_utils::auto_scope_leave_caller keys_reencryptor; @@ -5099,13 +5563,6 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor ); } - // open kex messages - std::vector<multisig::multisig_kex_msg> expanded_msgs; - expanded_msgs.reserve(kex_messages.size()); - - for (const auto &msg : kex_messages) - expanded_msgs.emplace_back(msg); - // reconstruct multisig account multisig::multisig_keyset_map_memsafe_t kex_origins_map; @@ -5126,8 +5583,25 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor "" }; + // KLUDGE: early return if there are no kex messages and main kex is complete (will return the post-kex verification round + // message) (it's a kludge because this behavior would be more appropriate for a standalone wallet method) + if (kex_messages.size() == 0) + { + CHECK_AND_ASSERT_THROW_MES(multisig_account.main_kex_rounds_done(), + "Exchange multisig keys: there are no kex messages but the main kex rounds are not done."); + + return multisig_account.get_next_kex_round_msg(); + } + + // open kex messages + std::vector<multisig::multisig_kex_msg> expanded_msgs; + expanded_msgs.reserve(kex_messages.size()); + + for (const auto &msg : kex_messages) + expanded_msgs.emplace_back(msg); + // update multisig kex - multisig_account.kex_update(expanded_msgs); + multisig_account.kex_update(expanded_msgs, force_update_use_with_caution); // update wallet state @@ -5208,7 +5682,7 @@ bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const if (ready) { *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())) && - (m_multisig_rounds_passed == multisig::multisig_kex_rounds_required(m_multisig_signers.size(), m_multisig_threshold) + 1); + (m_multisig_rounds_passed == multisig::multisig_setup_rounds_required(m_multisig_signers.size(), m_multisig_threshold)); } return true; } @@ -5324,7 +5798,7 @@ bool wallet2::prepare_file_names(const std::string& file_path) return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) +bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout, bool *wallet_is_outdated, bool *daemon_is_outdated) { THROW_WALLET_EXCEPTION_IF(!m_is_initialized, error::wallet_not_initialized); @@ -5338,16 +5812,6 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) return false; } - // TODO: Add light wallet version check. - if(m_light_wallet) { - m_rpc_version = 0; - if (version) - *version = 0; - if (ssl) - *ssl = m_light_wallet_connected; // light wallet is always SSL - return m_light_wallet_connected; - } - { boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); if(!m_http_client->is_connected(ssl)) @@ -5361,20 +5825,99 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) } } - if (!m_rpc_version) + if (!m_rpc_version && !check_version(version, wallet_is_outdated, daemon_is_outdated)) + return false; + if (version) + *version = m_rpc_version; + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated) +{ + uint32_t rpc_version; + std::vector<std::pair<uint8_t, uint64_t>> daemon_hard_forks; + uint64_t height; + uint64_t target_height; + if (m_node_rpc_proxy.get_rpc_version(rpc_version, daemon_hard_forks, height, target_height)) { - cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t); - cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t); - bool r = invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t); - if(!r || resp_t.status != CORE_RPC_STATUS_OK) { - if(version) - *version = 0; + if(version) + *version = 0; + return false; + } + + // check wallet compatibility with daemon's hard fork version + if (!m_allow_mismatched_daemon_version) + if (!check_hard_fork_version(m_nettype, daemon_hard_forks, height, target_height, wallet_is_outdated, daemon_is_outdated)) return false; + + m_rpc_version = rpc_version; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_hard_fork_version(cryptonote::network_type nettype, const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated) +{ + const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks + : nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks; + const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks + : nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks; + + // First check if wallet or daemon is outdated (whether either are unaware of + // a hard fork). Then check if fork has passed rendering versions incompatible + if (daemon_hard_forks.size() > 0) + { + bool daemon_outdated = daemon_hard_forks.size() < wallet_num_hard_forks; + bool wallet_outdated = daemon_hard_forks.size() > wallet_num_hard_forks; + + if (daemon_is_outdated) + *daemon_is_outdated = daemon_outdated; + if (wallet_is_outdated) + *wallet_is_outdated = wallet_outdated; + + if (daemon_outdated) + { + uint64_t daemon_missed_fork_height = wallet_hard_forks[daemon_hard_forks.size()].height; + + // If the daemon missed the fork, then technically it is no longer part of + // the Monero network. Don't connect. + bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height; + if (daemon_missed_fork) + return false; + } + else if (wallet_outdated) + { + uint64_t wallet_missed_fork_height = daemon_hard_forks[wallet_num_hard_forks].second; + + // If the wallet missed the fork, then technically it is no longer able + // to communicate with the Monero network. Don't connect. + bool wallet_missed_fork = height >= wallet_missed_fork_height || target_height >= wallet_missed_fork_height; + if (wallet_missed_fork) + return false; } - m_rpc_version = resp_t.version; } - if (version) - *version = m_rpc_version; + else + { + // Non-updated daemons won't return daemon_hard_forks in response to + // get_version. Fall back to extra call to get_hard_fork_info by version. + uint64_t daemon_fork_height; + get_hard_fork_info(wallet_num_hard_forks-1/* wallet expects "double fork" pattern */, daemon_fork_height); + bool daemon_outdated = daemon_fork_height == std::numeric_limits<uint64_t>::max(); + + if (daemon_is_outdated) + *daemon_is_outdated = daemon_outdated; + + if (daemon_outdated) + { + uint64_t daemon_missed_fork_height = wallet_hard_forks[wallet_num_hard_forks-2].height; + bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height; + if (daemon_missed_fork) + return false; + } + + // Don't need to check if wallet is outdated here because the daemons updated + // for a future hard fork will serve daemon_hard_forks above. The check for + // an outdated wallet is done above using daemon_hard_forks. + } return true; } @@ -5555,9 +6098,6 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); } - if (!m_persistent_rpc_client_id) - set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); - cryptonote::block genesis; generate_genesis(genesis); crypto::hash genesis_hash = get_block_hash(genesis); @@ -5608,27 +6148,16 @@ void wallet2::trim_hashchain() if (!m_blockchain.empty() && m_blockchain.size() == m_blockchain.offset()) { MINFO("Fixing empty hashchain"); - cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request req = AUTO_VAL_INIT(req); - cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response res = AUTO_VAL_INIT(res); - - bool r; - { - const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - req.height = m_blockchain.size() - 1; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); - r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, *m_http_client, rpc_timeout); - if (r && res.status == CORE_RPC_STATUS_OK) - check_rpc_cost("getblockheaderbyheight", res.credits, pre_call_credits, COST_PER_BLOCK_HEADER); - } - - if (r && res.status == CORE_RPC_STATUS_OK) + try { + cryptonote::block_header_response block_header; + if (m_node_rpc_proxy.get_block_header_by_height(m_blockchain.size() - 1, block_header)) + throw std::runtime_error("Failed to request block header by height"); crypto::hash hash; - epee::string_tools::hex_to_pod(res.block_header.hash, hash); + epee::string_tools::hex_to_pod(block_header.hash, hash); m_blockchain.refill(hash); } - else + catch(...) { MERROR("Failed to request block header from daemon, hash chain may be unable to sync till the wallet is loaded with a usable daemon"); } @@ -5801,8 +6330,6 @@ boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epe uint64_t wallet2::balance(uint32_t index_major, bool strict) const { uint64_t amount = 0; - if(m_light_wallet) - return m_light_wallet_balance; for (const auto& i : balance_per_subaddress(index_major, strict)) amount += i.second; return amount; @@ -5815,8 +6342,6 @@ uint64_t wallet2::unlocked_balance(uint32_t index_major, bool strict, uint64_t * *blocks_to_unlock = 0; if (time_to_unlock) *time_to_unlock = 0; - if(m_light_wallet) - return m_light_wallet_unlocked_balance; for (const auto& i : unlocked_balance_per_subaddress(index_major, strict)) { amount += i.second.first; @@ -6031,14 +6556,11 @@ void wallet2::rescan_spent() { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_resp, "is_key_image_spent", error::is_key_image_spent_error, get_rpc_status(daemon_resp.status)); THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != n_outputs, error::wallet_internal_error, "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(n_outputs)); - check_rpc_cost("/is_key_image_spent", daemon_resp.credits, pre_call_credits, n_outputs * COST_PER_KEY_IMAGE); } std::copy(daemon_resp.spent_status.begin(), daemon_resp.spent_status.end(), std::back_inserter(spent_status)); @@ -6354,50 +6876,47 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const void wallet2::commit_tx(pending_tx& ptx) { using namespace cryptonote; - - if(m_light_wallet) + + // Normal submit + COMMAND_RPC_SEND_RAW_TX::request req; + req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)); + req.do_not_relay = false; + req.do_sanity_checks = true; + COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; + { - cryptonote::COMMAND_RPC_SUBMIT_RAW_TX::request oreq; - cryptonote::COMMAND_RPC_SUBMIT_RAW_TX::response ores; - oreq.address = get_account().get_public_address_str(m_nettype); - oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); - oreq.tx = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)); - { - const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - bool r = epee::net_utils::invoke_http_json("/submit_raw_tx", oreq, ores, *m_http_client, rpc_timeout, "POST"); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx"); - // MyMonero and OpenMonero use different status strings - THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, get_rpc_status(ores.status), ores.error); - } + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, *m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_send_resp, "sendrawtransaction", error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp.status), get_text_reason(daemon_send_resp)); } - else - { - // Normal submit - COMMAND_RPC_SEND_RAW_TX::request req; - req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)); - req.do_not_relay = false; - req.do_sanity_checks = true; - COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; - { - const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); - bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, *m_http_client, rpc_timeout); - THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_send_resp, "sendrawtransaction", error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp.status), get_text_reason(daemon_send_resp)); - check_rpc_cost("/sendrawtransaction", daemon_send_resp.credits, pre_call_credits, COST_PER_TX_RELAY); - } - - // sanity checks - for (size_t idx: ptx.selected_transfers) - { - THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, - "Bad output index in selected transfers: " + boost::lexical_cast<std::string>(idx)); - } + // sanity checks + for (size_t idx: ptx.selected_transfers) + { + THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, + "Bad output index in selected transfers: " + boost::lexical_cast<std::string>(idx)); } crypto::hash txid; txid = get_transaction_hash(ptx.tx); + + // if it's already processed, bail + if (std::find_if(m_transfers.begin(), m_transfers.end(), [&txid](const transfer_details &td) { return td.m_txid == txid; }) != m_transfers.end()) + { + MDEBUG("Transaction " << txid << " already processed"); + return; + } + if (m_unconfirmed_txs.find(txid) != m_unconfirmed_txs.end()) + { + MDEBUG("Transaction " << txid << " already processed"); + return; + } + if (m_confirmed_txs.find(txid) != m_confirmed_txs.end()) + { + MDEBUG("Transaction " << txid << " already processed"); + return; + } + crypto::hash payment_id = crypto::null_hash; std::vector<cryptonote::tx_destination_entry> dests; uint64_t amount_in = 0; @@ -6604,9 +7123,9 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s //---------------------------------------------------------------------------------------------------- bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &txs, signed_tx_set &signed_txes) { - if (!exported_txs.new_transfers.second.empty()) + if (!std::get<2>(exported_txs.new_transfers).empty()) import_outputs(exported_txs.new_transfers); - else + else if (!std::get<2>(exported_txs.transfers).empty()) import_outputs(exported_txs.transfers); // sign the transactions @@ -7348,13 +7867,6 @@ uint64_t wallet2::get_dynamic_base_fee_estimate() //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_base_fee() { - if(m_light_wallet) - { - if (use_fork_rules(HF_VERSION_PER_BYTE_FEE)) - return m_light_wallet_per_kb_fee / 1024; - else - return m_light_wallet_per_kb_fee; - } bool use_dyn_fee = use_fork_rules(HF_VERSION_DYNAMIC_FEE, -30 * 1); if (!use_dyn_fee) return FEE_PER_KB; @@ -7398,10 +7910,6 @@ uint64_t wallet2::get_base_fee(uint32_t priority) //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_quantization_mask() { - if(m_light_wallet) - { - return 1; // TODO - } bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); if (!use_per_byte_fee) return 1; @@ -7509,11 +8017,8 @@ uint32_t wallet2::adjust_priority(uint32_t priority) { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - getbh_req.client = get_client_signature(); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, getbh_res, "getblockheadersrange", error::get_blocks_error, get_rpc_status(getbh_res.status)); - check_rpc_cost("/getblockheadersrange", getbh_res.credits, pre_call_credits, N * COST_PER_BLOCK_HEADER); } if (getbh_res.headers.size() != N) @@ -7752,14 +8257,11 @@ bool wallet2::find_and_save_rings(bool force) { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/gettransactions"); THROW_WALLET_EXCEPTION_IF(res.txs.size() != req.txs_hashes.size(), error::wallet_internal_error, "daemon returned wrong response for gettransactions, wrong txs count = " + std::to_string(res.txs.size()) + ", expected " + std::to_string(req.txs_hashes.size())); - check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX); } MDEBUG("Scanning " << res.txs.size() << " transactions"); @@ -7882,113 +8384,6 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out return true; } -void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count) { - - MDEBUG("LIGHTWALLET - Getting random outs"); - - tools::COMMAND_RPC_GET_RANDOM_OUTS::request oreq; - tools::COMMAND_RPC_GET_RANDOM_OUTS::response ores; - - size_t light_wallet_requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1); - - // Amounts to ask for - // MyMonero api handle amounts and fees as strings - for(size_t idx: selected_transfers) { - const uint64_t ask_amount = m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount(); - std::ostringstream amount_ss; - amount_ss << ask_amount; - oreq.amounts.push_back(amount_ss.str()); - } - - oreq.count = light_wallet_requested_outputs_count; - - { - const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - bool r = epee::net_utils::invoke_http_json("/get_random_outs", oreq, ores, *m_http_client, rpc_timeout, "POST"); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs"); - THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs received from light wallet node. Error: " + ores.Error); - } - - // Check if we got enough outputs for each amount - for(auto& out: ores.amount_outs) { - THROW_WALLET_EXCEPTION_IF(out.outputs.size() < light_wallet_requested_outputs_count , error::wallet_internal_error, "Not enough outputs for amount: " + boost::lexical_cast<std::string>(out.amount)); - MDEBUG(out.outputs.size() << " outputs for amount "+ boost::lexical_cast<std::string>(out.amount) + " received from light wallet node"); - } - - MDEBUG("selected transfers size: " << selected_transfers.size()); - - std::unordered_set<crypto::public_key> valid_public_keys_cache; - for(size_t idx: selected_transfers) - { - // Create new index - outs.push_back(std::vector<get_outs_entry>()); - outs.back().reserve(fake_outputs_count + 1); - - // add real output first - const transfer_details &td = m_transfers[idx]; - const uint64_t amount = td.is_rct() ? 0 : td.amount(); - outs.back().push_back(std::make_tuple(td.m_global_output_index, td.get_public_key(), rct::commit(td.amount(), td.m_mask))); - MDEBUG("added real output " << string_tools::pod_to_hex(td.get_public_key())); - - // Even if the lightwallet server returns random outputs, we pick them randomly. - std::vector<size_t> order; - order.resize(light_wallet_requested_outputs_count); - for (size_t n = 0; n < order.size(); ++n) - order[n] = n; - std::shuffle(order.begin(), order.end(), crypto::random_device{}); - - - LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs with amounts " << print_money(td.is_rct() ? 0 : td.amount())); - MDEBUG("OUTS SIZE: " << outs.back().size()); - for (size_t o = 0; o < light_wallet_requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o) - { - // Random pick - size_t i = order[o]; - - // Find which random output key to use - bool found_amount = false; - size_t amount_key; - for(amount_key = 0; amount_key < ores.amount_outs.size(); ++amount_key) - { - if(boost::lexical_cast<uint64_t>(ores.amount_outs[amount_key].amount) == amount) { - found_amount = true; - break; - } - } - THROW_WALLET_EXCEPTION_IF(!found_amount , error::wallet_internal_error, "Outputs for amount " + boost::lexical_cast<std::string>(ores.amount_outs[amount_key].amount) + " not found" ); - - LOG_PRINT_L2("Index " << i << "/" << light_wallet_requested_outputs_count << ": idx " << ores.amount_outs[amount_key].outputs[i].global_index << " (real " << td.m_global_output_index << "), unlocked " << "(always in light)" << ", key " << ores.amount_outs[0].outputs[i].public_key); - - // Convert light wallet string data to proper data structures - crypto::public_key tx_public_key; - rct::key mask = AUTO_VAL_INIT(mask); // decrypted mask - not used here - rct::key rct_commit = AUTO_VAL_INIT(rct_commit); - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, ores.amount_outs[amount_key].outputs[i].public_key), error::wallet_internal_error, "Invalid public_key"); - string_tools::hex_to_pod(ores.amount_outs[amount_key].outputs[i].public_key, tx_public_key); - const uint64_t global_index = ores.amount_outs[amount_key].outputs[i].global_index; - if(!light_wallet_parse_rct_str(ores.amount_outs[amount_key].outputs[i].rct, tx_public_key, 0, mask, rct_commit, false)) - rct_commit = rct::zeroCommit(td.amount()); - - if (tx_add_fake_output(outs, global_index, tx_public_key, rct_commit, td.m_global_output_index, true, valid_public_keys_cache)) { - MDEBUG("added fake output " << ores.amount_outs[amount_key].outputs[i].public_key); - MDEBUG("index " << global_index); - } - } - - THROW_WALLET_EXCEPTION_IF(outs.back().size() < fake_outputs_count + 1 , error::wallet_internal_error, "Not enough fake outputs found" ); - - // Real output is the first. Shuffle outputs - MTRACE(outs.back().size() << " outputs added. Sorting outputs by index:"); - std::sort(outs.back().begin(), outs.back().end(), [](const get_outs_entry &a, const get_outs_entry &b) { return std::get<0>(a) < std::get<0>(b); }); - - // Print output order - for(auto added_out: outs.back()) - MTRACE(std::get<0>(added_out)); - - } -} - std::pair<std::set<uint64_t>, size_t> outs_unique(const std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs) { std::set<uint64_t> unique; @@ -8039,11 +8434,6 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); - if(m_light_wallet && fake_outputs_count > 0) { - light_wallet_get_outs(outs, selected_transfers, fake_outputs_count); - return; - } - if (fake_outputs_count > 0) { uint64_t segregation_fork_height = get_segregation_fork_height(); @@ -8073,7 +8463,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> if (has_rct) { // check we're clear enough of rct start, to avoid corner cases below - THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, + THROW_WALLET_EXCEPTION_IF(rct_offsets.size() < std::max(1, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE), error::get_output_distribution, "Not enough rct outputs"); THROW_WALLET_EXCEPTION_IF(rct_offsets.back() <= max_rct_index, error::get_output_distribution, "Daemon reports suspicious number of rct outputs"); @@ -8097,11 +8487,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req_t.client = get_client_signature(); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, get_rpc_status(resp_t.status)); - check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, COST_PER_OUTPUT_HISTOGRAM * req_t.amounts.size()); } } @@ -8124,13 +8511,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req_t.client = get_client_signature(); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, *m_http_client, rpc_timeout * 1000); THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_distribution", error::get_output_distribution, get_rpc_status(resp_t.status)); - uint64_t expected_cost = 0; - for (uint64_t amount: req_t.amounts) expected_cost += (amount ? COST_PER_OUTPUT_DISTRIBUTION : COST_PER_OUTPUT_DISTRIBUTION_0); - check_rpc_cost("get_output_distribution", resp_t.credits, pre_call_credits, expected_cost); } // check we got all data @@ -8268,7 +8650,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> else { // the base offset of the first rct output in the first unlocked block (or the one to be if there's none) - num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE]; + num_outs = gamma->get_num_rct_outs(); LOG_PRINT_L1("" << num_outs << " unlocked rct outputs"); THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, "histogram reports no unlocked rct outputs, not even ours"); @@ -8512,14 +8894,11 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> chunk_req.outputs.push_back(req.outputs[offset + i]); const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - chunk_req.client = get_client_signature(); bool r = epee::net_utils::invoke_http_bin("/get_outs.bin", chunk_req, chunk_daemon_resp, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, chunk_daemon_resp, "get_outs.bin", error::get_outs_error, get_rpc_status(chunk_daemon_resp.status)); THROW_WALLET_EXCEPTION_IF(chunk_daemon_resp.outs.size() != chunk_req.outputs.size(), error::wallet_internal_error, "daemon returned wrong response for get_outs.bin, wrong amounts count = " + std::to_string(chunk_daemon_resp.outs.size()) + ", expected " + std::to_string(chunk_req.outputs.size())); - check_rpc_cost("/get_outs.bin", chunk_daemon_resp.credits, pre_call_credits, chunk_daemon_resp.outs.size() * COST_PER_OUT); offset += chunk_size; for (size_t i = 0; i < chunk_daemon_resp.outs.size(); ++i) @@ -8552,7 +8931,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } bool use_histogram = amount != 0; if (!use_histogram) - num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE]; + num_outs = gamma->get_num_rct_outs(); // make sure the real outputs we asked for are really included, along // with the correct key and mask: this guards against an active attack @@ -9314,476 +9693,6 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr return count; } -bool wallet2::light_wallet_login(bool &new_address) -{ - MDEBUG("Light wallet login request"); - m_light_wallet_connected = false; - tools::COMMAND_RPC_LOGIN::request request; - tools::COMMAND_RPC_LOGIN::response response; - request.address = get_account().get_public_address_str(m_nettype); - request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); - // Always create account if it doesn't exist. - request.create_account = true; - m_daemon_rpc_mutex.lock(); - bool connected = invoke_http_json("/login", request, response, rpc_timeout, "POST"); - m_daemon_rpc_mutex.unlock(); - // MyMonero doesn't send any status message. OpenMonero does. - m_light_wallet_connected = connected && (response.status.empty() || response.status == "success"); - new_address = response.new_address; - MDEBUG("Status: " << response.status); - MDEBUG("Reason: " << response.reason); - MDEBUG("New wallet: " << response.new_address); - if(m_light_wallet_connected) - { - // Clear old data on successful login. - // m_transfers.clear(); - // m_payments.clear(); - // m_unconfirmed_payments.clear(); - } - return m_light_wallet_connected; -} - -bool wallet2::light_wallet_import_wallet_request(tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response) -{ - MDEBUG("Light wallet import wallet request"); - tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::request oreq; - oreq.address = get_account().get_public_address_str(m_nettype); - oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/import_wallet_request", oreq, response, rpc_timeout, "POST"); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "import_wallet_request"); - - - return true; -} - -void wallet2::light_wallet_get_unspent_outs() -{ - MDEBUG("Getting unspent outs"); - - tools::COMMAND_RPC_GET_UNSPENT_OUTS::request oreq; - tools::COMMAND_RPC_GET_UNSPENT_OUTS::response ores; - - oreq.amount = "0"; - oreq.address = get_account().get_public_address_str(m_nettype); - oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); - // openMonero specific - oreq.dust_threshold = boost::lexical_cast<std::string>(::config::DEFAULT_DUST_THRESHOLD); - // below are required by openMonero api - but are not used. - oreq.mixin = 0; - oreq.use_dust = true; - - - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/get_unspent_outs", oreq, ores, rpc_timeout, "POST"); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_unspent_outs"); - THROW_WALLET_EXCEPTION_IF(ores.status == "error", error::wallet_internal_error, ores.reason); - - m_light_wallet_per_kb_fee = ores.per_kb_fee; - - std::unordered_map<crypto::hash,bool> transfers_txs; - for(const auto &t: m_transfers) - transfers_txs.emplace(t.m_txid,t.m_spent); - - MDEBUG("FOUND " << ores.outputs.size() <<" outputs"); - - // return if no outputs found - if(ores.outputs.empty()) - return; - - // Clear old outputs - m_transfers.clear(); - - for (const auto &o: ores.outputs) { - bool spent = false; - bool add_transfer = true; - crypto::key_image unspent_key_image; - crypto::public_key tx_public_key = AUTO_VAL_INIT(tx_public_key); - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field"); - string_tools::hex_to_pod(o.tx_pub_key, tx_public_key); - - for (const std::string &ski: o.spend_key_images) { - spent = false; - - // Check if key image is ours - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, ski), error::wallet_internal_error, "Invalid key image"); - string_tools::hex_to_pod(ski, unspent_key_image); - if(light_wallet_key_image_is_ours(unspent_key_image, tx_public_key, o.index)){ - MTRACE("Output " << o.public_key << " is spent. Key image: " << ski); - spent = true; - break; - } { - MTRACE("Unspent output found. " << o.public_key); - } - } - - // Check if tx already exists in m_transfers. - crypto::hash txid; - crypto::public_key tx_pub_key; - crypto::public_key public_key; - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_hash), error::wallet_internal_error, "Invalid tx_hash field"); - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.public_key), error::wallet_internal_error, "Invalid public_key field"); - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field"); - string_tools::hex_to_pod(o.tx_hash, txid); - string_tools::hex_to_pod(o.public_key, public_key); - string_tools::hex_to_pod(o.tx_pub_key, tx_pub_key); - - for(auto &t: m_transfers){ - if(t.get_public_key() == public_key) { - t.m_spent = spent; - add_transfer = false; - break; - } - } - - if(!add_transfer) - continue; - - m_transfers.push_back(transfer_details{}); - transfer_details& td = m_transfers.back(); - - td.m_block_height = o.height; - td.m_global_output_index = o.global_index; - td.m_txid = txid; - - // Add to extra - add_tx_pub_key_to_extra(td.m_tx, tx_pub_key); - - td.m_key_image = unspent_key_image; - td.m_key_image_known = !m_watch_only && !m_multisig; - td.m_key_image_request = false; - td.m_key_image_partial = m_multisig; - td.m_amount = o.amount; - td.m_pk_index = 0; - td.m_internal_output_index = o.index; - td.m_spent = spent; - td.m_frozen = false; - - tx_out txout; - txout.target = txout_to_key(public_key); - txout.amount = td.m_amount; - - td.m_tx.vout.resize(td.m_internal_output_index + 1); - td.m_tx.vout[td.m_internal_output_index] = txout; - - // Add unlock time and coinbase bool got from get_address_txs api call - std::unordered_map<crypto::hash,address_tx>::const_iterator found = m_light_wallet_address_txs.find(txid); - THROW_WALLET_EXCEPTION_IF(found == m_light_wallet_address_txs.end(), error::wallet_internal_error, "Lightwallet: tx not found in m_light_wallet_address_txs"); - bool miner_tx = found->second.m_coinbase; - td.m_tx.unlock_time = found->second.m_unlock_time; - - if (!o.rct.empty()) - { - // Coinbase tx's - if(miner_tx) - { - td.m_mask = rct::identity(); - } - else - { - // rct txs - // decrypt rct mask, calculate commit hash and compare against blockchain commit hash - rct::key rct_commit; - light_wallet_parse_rct_str(o.rct, tx_pub_key, td.m_internal_output_index, td.m_mask, rct_commit, true); - bool valid_commit = (rct_commit == rct::commit(td.amount(), td.m_mask)); - if(!valid_commit) - { - MDEBUG("output index: " << o.global_index); - MDEBUG("mask: " + string_tools::pod_to_hex(td.m_mask)); - MDEBUG("calculated commit: " + string_tools::pod_to_hex(rct::commit(td.amount(), td.m_mask))); - MDEBUG("expected commit: " + string_tools::pod_to_hex(rct_commit)); - MDEBUG("amount: " << td.amount()); - } - THROW_WALLET_EXCEPTION_IF(!valid_commit, error::wallet_internal_error, "Lightwallet: rct commit hash mismatch!"); - } - td.m_rct = true; - } - else - { - td.m_mask = rct::identity(); - td.m_rct = false; - } - if(!spent) - set_unspent(m_transfers.size()-1); - m_key_images[td.m_key_image] = m_transfers.size()-1; - m_pub_keys[td.get_public_key()] = m_transfers.size()-1; - } -} - -bool wallet2::light_wallet_get_address_info(tools::COMMAND_RPC_GET_ADDRESS_INFO::response &response) -{ - MTRACE(__FUNCTION__); - - tools::COMMAND_RPC_GET_ADDRESS_INFO::request request; - - request.address = get_account().get_public_address_str(m_nettype); - request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/get_address_info", request, response, rpc_timeout, "POST"); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_address_info"); - // TODO: Validate result - return true; -} - -void wallet2::light_wallet_get_address_txs() -{ - MDEBUG("Refreshing light wallet"); - - tools::COMMAND_RPC_GET_ADDRESS_TXS::request ireq; - tools::COMMAND_RPC_GET_ADDRESS_TXS::response ires; - - ireq.address = get_account().get_public_address_str(m_nettype); - ireq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/get_address_txs", ireq, ires, rpc_timeout, "POST"); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_address_txs"); - //OpenMonero sends status=success, Mymonero doesn't. - THROW_WALLET_EXCEPTION_IF((!ires.status.empty() && ires.status != "success"), error::no_connection_to_daemon, "get_address_txs"); - - - // Abort if no transactions - if(ires.transactions.empty()) - return; - - // Create searchable vectors - std::vector<crypto::hash> payments_txs; - for(const auto &p: m_payments) - payments_txs.push_back(p.second.m_tx_hash); - std::vector<crypto::hash> unconfirmed_payments_txs; - for(const auto &up: m_unconfirmed_payments) - unconfirmed_payments_txs.push_back(up.second.m_pd.m_tx_hash); - - // for balance calculation - uint64_t wallet_total_sent = 0; - // txs in pool - std::vector<crypto::hash> pool_txs; - - for (const auto &t: ires.transactions) { - const uint64_t total_received = t.total_received; - uint64_t total_sent = t.total_sent; - - // Check key images - subtract fake outputs from total_sent - for(const auto &so: t.spent_outputs) - { - crypto::public_key tx_public_key; - crypto::key_image key_image; - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, so.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field"); - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, so.key_image), error::wallet_internal_error, "Invalid key_image field"); - string_tools::hex_to_pod(so.tx_pub_key, tx_public_key); - string_tools::hex_to_pod(so.key_image, key_image); - - if(!light_wallet_key_image_is_ours(key_image, tx_public_key, so.out_index)) { - THROW_WALLET_EXCEPTION_IF(so.amount > t.total_sent, error::wallet_internal_error, "Lightwallet: total sent is negative!"); - total_sent -= so.amount; - } - } - - // Do not add tx if empty. - if(total_sent == 0 && total_received == 0) - continue; - - crypto::hash payment_id = null_hash; - crypto::hash tx_hash; - - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, t.payment_id), error::wallet_internal_error, "Invalid payment_id field"); - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, t.hash), error::wallet_internal_error, "Invalid hash field"); - string_tools::hex_to_pod(t.payment_id, payment_id); - string_tools::hex_to_pod(t.hash, tx_hash); - - // lightwallet specific info - bool incoming = (total_received > total_sent); - address_tx address_tx; - address_tx.m_tx_hash = tx_hash; - address_tx.m_incoming = incoming; - address_tx.m_amount = incoming ? total_received - total_sent : total_sent - total_received; - address_tx.m_fee = 0; // TODO - address_tx.m_block_height = t.height; - address_tx.m_unlock_time = t.unlock_time; - address_tx.m_timestamp = t.timestamp; - address_tx.m_coinbase = t.coinbase; - address_tx.m_mempool = t.mempool; - m_light_wallet_address_txs.emplace(tx_hash,address_tx); - - // populate data needed for history (m_payments, m_unconfirmed_payments, m_confirmed_txs) - // INCOMING transfers - if(total_received > total_sent) { - payment_details payment; - payment.m_tx_hash = tx_hash; - payment.m_amount = total_received - total_sent; - payment.m_fee = 0; // TODO - payment.m_block_height = t.height; - payment.m_unlock_time = t.unlock_time; - payment.m_timestamp = t.timestamp; - payment.m_coinbase = t.coinbase; - - if (t.mempool) { - if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) { - pool_txs.push_back(tx_hash); - // assume false as we don't get that info from the light wallet server - crypto::hash payment_id; - THROW_WALLET_EXCEPTION_IF(!epee::string_tools::hex_to_pod(t.payment_id, payment_id), - error::wallet_internal_error, "Failed to parse payment id"); - emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, false}); - if (0 != m_callback) { - m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount); - } - } - } else { - if (std::find(payments_txs.begin(), payments_txs.end(), tx_hash) == payments_txs.end()) { - m_payments.emplace(tx_hash, payment); - if (0 != m_callback) { - m_callback->on_lw_money_received(t.height, payment.m_tx_hash, payment.m_amount); - } - } - } - // Outgoing transfers - } else { - uint64_t amount_sent = total_sent - total_received; - cryptonote::transaction dummy_tx; // not used by light wallet - // increase wallet total sent - wallet_total_sent += total_sent; - if (t.mempool) - { - // Handled by add_unconfirmed_tx in commit_tx - // If sent from another wallet instance we need to add it - if(m_unconfirmed_txs.find(tx_hash) == m_unconfirmed_txs.end()) - { - unconfirmed_transfer_details utd; - utd.m_amount_in = amount_sent; - utd.m_amount_out = amount_sent; - utd.m_change = 0; - utd.m_payment_id = payment_id; - utd.m_timestamp = t.timestamp; - utd.m_state = wallet2::unconfirmed_transfer_details::pending; - m_unconfirmed_txs.emplace(tx_hash,utd); - } - } - else - { - // Only add if new - auto confirmed_tx = m_confirmed_txs.find(tx_hash); - if(confirmed_tx == m_confirmed_txs.end()) { - // tx is added to m_unconfirmed_txs - move to confirmed - if(m_unconfirmed_txs.find(tx_hash) != m_unconfirmed_txs.end()) - { - process_unconfirmed(tx_hash, dummy_tx, t.height); - } - // Tx sent by another wallet instance - else - { - confirmed_transfer_details ctd; - ctd.m_amount_in = amount_sent; - ctd.m_amount_out = amount_sent; - ctd.m_change = 0; - ctd.m_payment_id = payment_id; - ctd.m_block_height = t.height; - ctd.m_timestamp = t.timestamp; - m_confirmed_txs.emplace(tx_hash,ctd); - } - if (0 != m_callback) - { - m_callback->on_lw_money_spent(t.height, tx_hash, amount_sent); - } - } - // If not new - check the amount and update if necessary. - // when sending a tx to same wallet the receiving amount has to be credited - else - { - if(confirmed_tx->second.m_amount_in != amount_sent || confirmed_tx->second.m_amount_out != amount_sent) - { - MDEBUG("Adjusting amount sent/received for tx: <" + t.hash + ">. Is tx sent to own wallet? " << print_money(amount_sent) << " != " << print_money(confirmed_tx->second.m_amount_in)); - confirmed_tx->second.m_amount_in = amount_sent; - confirmed_tx->second.m_amount_out = amount_sent; - confirmed_tx->second.m_change = 0; - } - } - } - } - } - // TODO: purge old unconfirmed_txs - remove_obsolete_pool_txs(pool_txs); - - // Calculate wallet balance - m_light_wallet_balance = ires.total_received-wallet_total_sent; - // MyMonero doesn't send unlocked balance - if(ires.total_received_unlocked > 0) - m_light_wallet_unlocked_balance = ires.total_received_unlocked-wallet_total_sent; - else - m_light_wallet_unlocked_balance = m_light_wallet_balance; -} - -bool wallet2::light_wallet_parse_rct_str(const std::string& rct_string, const crypto::public_key& tx_pub_key, uint64_t internal_output_index, rct::key& decrypted_mask, rct::key& rct_commit, bool decrypt) const -{ - // rct string is empty if output is non RCT - if (rct_string.empty()) - return false; - // rct_string is a string with length 64+64+64 (<rct commit> + <encrypted mask> + <rct amount>) - rct::key encrypted_mask; - std::string rct_commit_str = rct_string.substr(0,64); - std::string encrypted_mask_str = rct_string.substr(64,64); - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, rct_commit_str), error::wallet_internal_error, "Invalid rct commit hash: " + rct_commit_str); - THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, encrypted_mask_str), error::wallet_internal_error, "Invalid rct mask: " + encrypted_mask_str); - string_tools::hex_to_pod(rct_commit_str, rct_commit); - string_tools::hex_to_pod(encrypted_mask_str, encrypted_mask); - if (decrypt) { - // Decrypt the mask - crypto::key_derivation derivation; - bool r = generate_key_derivation(tx_pub_key, get_account().get_keys().m_view_secret_key, derivation); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); - crypto::secret_key scalar; - crypto::derivation_to_scalar(derivation, internal_output_index, scalar); - sc_sub(decrypted_mask.bytes,encrypted_mask.bytes,rct::hash_to_scalar(rct::sk2rct(scalar)).bytes); - } - return true; -} - -bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, const crypto::public_key& tx_public_key, uint64_t out_index) -{ - // Lookup key image from cache - serializable_map<uint64_t, crypto::key_image> index_keyimage_map; - serializable_unordered_map<crypto::public_key, serializable_map<uint64_t, crypto::key_image> >::const_iterator found_pub_key = m_key_image_cache.find(tx_public_key); - if(found_pub_key != m_key_image_cache.end()) { - // pub key found. key image for index cached? - index_keyimage_map = found_pub_key->second; - std::map<uint64_t,crypto::key_image>::const_iterator index_found = index_keyimage_map.find(out_index); - if(index_found != index_keyimage_map.end()) - return key_image == index_found->second; - } - - // Not in cache - calculate key image - crypto::key_image calculated_key_image; - cryptonote::keypair in_ephemeral; - - // Subaddresses aren't supported in mymonero/openmonero yet. Roll out the original scheme: - // compute D = a*R - // compute P = Hs(D || i)*G + B - // compute x = Hs(D || i) + b (and check if P==x*G) - // compute I = x*Hp(P) - const account_keys& ack = get_account().get_keys(); - crypto::key_derivation derivation; - bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, derivation); - CHECK_AND_ASSERT_MES(r, false, "failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); - - r = crypto::derive_public_key(derivation, out_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key (" << derivation << ", " << out_index << ", " << ack.m_account_address.m_spend_public_key << ")"); - - crypto::derive_secret_key(derivation, out_index, ack.m_spend_secret_key, in_ephemeral.sec); - crypto::public_key out_pkey_test; - r = crypto::secret_key_to_public_key(in_ephemeral.sec, out_pkey_test); - CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_key(" << in_ephemeral.sec << ")"); - CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_pkey_test, false, "derived secret key doesn't match derived public key"); - - crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, calculated_key_image); - - index_keyimage_map.emplace(out_index, calculated_key_image); - m_key_image_cache.emplace(tx_public_key, index_keyimage_map); - return key_image == calculated_key_image; -} - // Another implementation of transaction creation that is hopefully better // While there is anything left to pay, it goes through random outputs and tries // to fill the next destination/amount. If it fully fills it, it will use the @@ -9808,10 +9717,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp auto original_dsts = dsts; - if(m_light_wallet) { - // Populate m_transfers - light_wallet_get_unspent_outs(); - } std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_transfers_indices_per_subaddr; std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_dust_indices_per_subaddr; uint64_t needed_money; @@ -10800,7 +10705,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_ { txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device())); } - txs.transfers = std::make_pair(0, m_transfers); + txs.transfers = std::make_tuple(0, m_transfers.size(), m_transfers); auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev); CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface"); @@ -10874,9 +10779,6 @@ void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) //---------------------------------------------------------------------------------------------------- bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) { - // TODO: How to get fork rule info from light wallet node? - if(m_light_wallet) - return true; uint64_t height, earliest_height; boost::optional<std::string> result = m_node_rpc_proxy.get_height(height); THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get height"); @@ -10952,12 +10854,8 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req_t.client = get_client_signature(); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, resp_t.status); - uint64_t cost = req_t.amounts.empty() ? COST_PER_FULL_OUTPUT_HISTOGRAM : (COST_PER_OUTPUT_HISTOGRAM * req_t.amounts.size()); - check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, cost); } std::set<uint64_t> mixable; @@ -10994,13 +10892,10 @@ uint64_t wallet2::get_num_rct_outputs() { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req_t.client = get_client_signature(); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, resp_t.status); THROW_WALLET_EXCEPTION_IF(resp_t.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); THROW_WALLET_EXCEPTION_IF(resp_t.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); - check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, COST_PER_OUTPUT_HISTOGRAM); } return resp_t.histogram[0].total_instances; @@ -11125,12 +11020,9 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - req.client = get_client_signature(); - uint64_t pre_call_credits = m_rpc_payment_state.credits; bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); - check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX); } cryptonote::transaction tx; @@ -11175,17 +11067,13 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_ req.prune = true; COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; - uint64_t pre_call_credits; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/gettransactions"); THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, "daemon returned wrong response for gettransactions, wrong txs count = " + std::to_string(res.txs.size()) + ", expected 1"); - check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); } cryptonote::transaction tx; @@ -11238,17 +11126,13 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string req.prune = true; COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; - uint64_t pre_call_credits; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "gettransactions"); THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, "daemon returned wrong response for gettransactions, wrong txs count = " + std::to_string(res.txs.size()) + ", expected 1"); - check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); } cryptonote::transaction tx; @@ -11301,17 +11185,13 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string } COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); bool r; - uint64_t pre_call_credits; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_outs.bin", error::get_outs_error, res.status); THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error, "daemon returned wrong response for get_outs.bin, wrong amounts count = " + std::to_string(res.outs.size()) + ", expected " + std::to_string(ring_size)); - check_rpc_cost("/get_outs.bin", res.credits, pre_call_credits, ring_size * COST_PER_OUT); } // copy pubkey pointers @@ -11359,17 +11239,13 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes req.prune = true; COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; - uint64_t pre_call_credits; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "gettransactions"); THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, "daemon returned wrong response for gettransactions, wrong txs count = " + std::to_string(res.txs.size()) + ", expected 1"); - check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); } cryptonote::transaction tx; @@ -11434,17 +11310,13 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes } COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); bool r; - uint64_t pre_call_credits; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_outs.bin", error::get_outs_error, res.status); THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error, "daemon returned wrong response for get_outs.bin, wrong amounts count = " + std::to_string(res.outs.size()) + ", expected " + std::to_string(req.outputs.size())); - check_rpc_cost("/get_outs.bin", res.credits, pre_call_credits, req.outputs.size() * COST_PER_OUT); } // copy pointers @@ -11530,12 +11402,9 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de bool ok; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); - check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); } cryptonote::transaction tx; @@ -11628,12 +11497,9 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac bool ok; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); ok = net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); - check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); } cryptonote::transaction tx; @@ -11789,12 +11655,9 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account bool ok; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); ok = net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); - check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); } cryptonote::transaction tx; @@ -12112,12 +11975,9 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - gettx_req.client = get_client_signature(); bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, *m_http_client); THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(), error::wallet_internal_error, "Failed to get transaction from daemon"); - check_rpc_cost("/gettransactions", gettx_res.credits, pre_call_credits, gettx_res.txs.size() * COST_PER_TX); } // check spent status @@ -12129,12 +11989,9 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr bool ok; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - kispent_req.client = get_client_signature(); ok = epee::net_utils::invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, *m_http_client, rpc_timeout); THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(), error::wallet_internal_error, "Failed to get key image spent status from daemon"); - check_rpc_cost("/is_key_image_spent", kispent_res.credits, pre_call_credits, kispent_res.spent_status.size() * COST_PER_KEY_IMAGE); } total = spent = 0; @@ -12784,14 +12641,11 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag PERF_TIMER(import_key_images_RPC); { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, daemon_resp, "is_key_image_spent"); THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != signed_key_images.size(), error::wallet_internal_error, "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(signed_key_images.size())); - check_rpc_cost("/is_key_image_spent", daemon_resp.credits, pre_call_credits, daemon_resp.spent_status.size() * COST_PER_KEY_IMAGE); } for (size_t n = 0; n < daemon_resp.spent_status.size(); ++n) @@ -12873,13 +12727,10 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag PERF_TIMER_START(import_key_images_E); { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - gettxs_req.client = get_client_signature(); - uint64_t pre_call_credits = m_rpc_payment_state.credits; bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, gettxs_res, "gettransactions"); THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error, "daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size())); - check_rpc_cost("/gettransactions", gettxs_res.credits, pre_call_credits, spent_txids.size() * COST_PER_TX); } PERF_TIMER_STOP(import_key_images_E); @@ -13103,18 +12954,29 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); } //---------------------------------------------------------------------------------------------------- -std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all) const +std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all, uint32_t start, uint32_t count) const { PERF_TIMER(export_outputs); std::vector<tools::wallet2::exported_transfer_details> outs; + // invalid cases + THROW_WALLET_EXCEPTION_IF(count == 0, error::wallet_internal_error, "Nothing requested"); + THROW_WALLET_EXCEPTION_IF(!all && start > 0, error::wallet_internal_error, "Incremental mode is incompatible with non-zero start"); + + // valid cases: + // all: all outputs, subject to start/count + // !all: incremental, subject to count + // for convenience, start/count are allowed to go past the valid range, then nothing is returned + size_t offset = 0; if (!all) while (offset < m_transfers.size() && (m_transfers[offset].m_key_image_known && !m_transfers[offset].m_key_image_request)) ++offset; + else + offset = start; outs.reserve(m_transfers.size() - offset); - for (size_t n = offset; n < m_transfers.size(); ++n) + for (size_t n = offset; n < m_transfers.size() && n - offset < count; ++n) { const transfer_details &td = m_transfers[n]; @@ -13132,20 +12994,22 @@ std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wall etd.m_flags.m_key_image_partial = td.m_key_image_partial; etd.m_amount = td.m_amount; etd.m_additional_tx_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + etd.m_subaddr_index_major = td.m_subaddr_index.major; + etd.m_subaddr_index_minor = td.m_subaddr_index.minor; outs.push_back(etd); } - return std::make_pair(offset, outs); + return std::make_tuple(offset, m_transfers.size(), outs); } //---------------------------------------------------------------------------------------------------- -std::string wallet2::export_outputs_to_str(bool all) const +std::string wallet2::export_outputs_to_str(bool all, uint32_t start, uint32_t count) const { PERF_TIMER(export_outputs_to_str); std::stringstream oss; binary_archive<true> ar(oss); - auto outputs = export_outputs(all); + auto outputs = export_outputs(all, start, count); THROW_WALLET_EXCEPTION_IF(!::serialization::serialize(ar, outputs), error::wallet_internal_error, "Failed to serialize output data"); std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); @@ -13158,21 +13022,33 @@ std::string wallet2::export_outputs_to_str(bool all) const return magic + ciphertext; } //---------------------------------------------------------------------------------------------------- -size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs) +size_t wallet2::import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs) { PERF_TIMER(import_outputs); - THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(m_has_ever_refreshed_from_node, error::wallet_internal_error, + "Hot wallets cannot import outputs"); + + // we can now import piecemeal + const size_t offset = std::get<0>(outputs); + const size_t num_outputs = std::get<1>(outputs); + const std::vector<tools::wallet2::transfer_details> &output_array = std::get<2>(outputs); + + THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Imported outputs omit more outputs that we know of"); - const size_t offset = outputs.first; + THROW_WALLET_EXCEPTION_IF(offset + output_array.size() > num_outputs, error::wallet_internal_error, + "Offset is larger than total outputs"); + const size_t original_size = m_transfers.size(); - m_transfers.resize(offset + outputs.second.size()); - for (size_t i = 0; i < offset; ++i) - m_transfers[i].m_key_image_request = false; - for (size_t i = 0; i < outputs.second.size(); ++i) + if (offset + output_array.size() > m_transfers.size()) + m_transfers.resize(offset + output_array.size()); + else if (num_outputs < m_transfers.size()) + m_transfers.resize(num_outputs); + + for (size_t i = 0; i < output_array.size(); ++i) { - transfer_details td = outputs.second[i]; + transfer_details td = output_array[i]; // skip those we've already imported, or which have different data if (i + offset < original_size) @@ -13206,6 +13082,8 @@ process: THROW_WALLET_EXCEPTION_IF(td.m_internal_output_index >= td.m_tx.vout.size(), error::wallet_internal_error, "Internal index is out of range"); crypto::public_key out_key = td.get_public_key(); + if (should_expand(td.m_subaddr_index)) + create_one_off_subaddress(td.m_subaddr_index); bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); if (should_expand(td.m_subaddr_index)) @@ -13224,24 +13102,36 @@ process: return m_transfers.size(); } //---------------------------------------------------------------------------------------------------- -size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs) +size_t wallet2::import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs) { PERF_TIMER(import_outputs); - THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(m_has_ever_refreshed_from_node, error::wallet_internal_error, + "Hot wallets cannot import outputs"); + + // we can now import piecemeal + const size_t offset = std::get<0>(outputs); + const size_t num_outputs = std::get<1>(outputs); + const std::vector<tools::wallet2::exported_transfer_details> &output_array = std::get<2>(outputs); + + THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Imported outputs omit more outputs that we know of. Try using export_outputs all."); - const size_t offset = outputs.first; + THROW_WALLET_EXCEPTION_IF(offset + output_array.size() > num_outputs, error::wallet_internal_error, + "Offset is larger than total outputs"); + const size_t original_size = m_transfers.size(); - m_transfers.resize(offset + outputs.second.size()); - for (size_t i = 0; i < offset; ++i) - m_transfers[i].m_key_image_request = false; - for (size_t i = 0; i < outputs.second.size(); ++i) + if (offset + output_array.size() > m_transfers.size()) + m_transfers.resize(offset + output_array.size()); + else if (num_outputs < m_transfers.size()) + m_transfers.resize(num_outputs); + + for (size_t i = 0; i < output_array.size(); ++i) { - exported_transfer_details etd = outputs.second[i]; + exported_transfer_details etd = output_array[i]; transfer_details &td = m_transfers[i + offset]; - // setup td with "cheao" loaded data + // setup td with "cheap" loaded data td.m_block_height = 0; td.m_txid = crypto::null_hash; td.m_global_output_index = etd.m_global_output_index; @@ -13254,6 +13144,8 @@ size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wall td.m_key_image_known = etd.m_flags.m_key_image_known; td.m_key_image_request = etd.m_flags.m_key_image_request; td.m_key_image_partial = false; + td.m_subaddr_index.major = etd.m_subaddr_index_major; + td.m_subaddr_index.minor = etd.m_subaddr_index_minor; // skip those we've already imported, or which have different data if (i + offset < original_size) @@ -13294,6 +13186,8 @@ size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wall const crypto::public_key &tx_pub_key = etd.m_tx_pubkey; const std::vector<crypto::public_key> &additional_tx_pub_keys = etd.m_additional_tx_keys; const crypto::public_key& out_key = etd.m_pubkey; + if (should_expand(td.m_subaddr_index)) + create_one_off_subaddress(td.m_subaddr_index); bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); if (should_expand(td.m_subaddr_index)) @@ -13350,7 +13244,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) { std::string body(data, headerlen); - std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs; + std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs; try { binary_archive<false> ar{epee::strspan<std::uint8_t>(body)}; @@ -13360,9 +13254,9 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) } catch (...) {} if (!loaded) - new_outputs.second.clear(); + std::get<2>(new_outputs).clear(); - std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; + std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; if (!loaded) try { binary_archive<false> ar{epee::strspan<std::uint8_t>(body)}; @@ -13387,15 +13281,16 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) if (!loaded) { - outputs.first = 0; - outputs.second = {}; + std::get<0>(outputs) = 0; + std::get<1>(outputs) = 0; + std::get<2>(outputs) = {}; } - imported_outputs = new_outputs.second.empty() ? import_outputs(outputs) : import_outputs(new_outputs); + imported_outputs = !std::get<2>(new_outputs).empty() ? import_outputs(new_outputs) : !std::get<2>(outputs).empty() ? import_outputs(outputs) : 0; } catch (const std::exception &e) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs") + e.what()); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs: ") + e.what()); } return imported_outputs; @@ -13709,7 +13604,7 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs) if (!td.m_key_image_partial) continue; MINFO("Multisig info importing from block height " << td.m_block_height); - detach_blockchain(td.m_block_height); + handle_reorg(td.m_block_height); break; } @@ -13985,11 +13880,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui bool r; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); r = net_utils::invoke_http_bin("/getblocks_by_height.bin", req, res, *m_http_client, rpc_timeout); - if (r && res.status == CORE_RPC_STATUS_OK) - check_rpc_cost("/getblocks_by_height.bin", res.credits, pre_call_credits, 3 * COST_PER_BLOCK); } if (!r || res.status != CORE_RPC_STATUS_OK) @@ -14063,11 +13954,8 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(const std:: { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_txpool_backlog", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_txpool_backlog", error::get_tx_pool_error); - check_rpc_cost("get_txpool_backlog", res.credits, pre_call_credits, COST_PER_TX_POOL_STATS * res.backlog.size()); } uint64_t block_weight_limit = 0; @@ -14218,13 +14106,17 @@ std::string wallet2::get_rpc_status(const std::string &s) const //---------------------------------------------------------------------------------------------------- void wallet2::throw_on_rpc_response_error(bool r, const epee::json_rpc::error &error, const std::string &status, const char *method) const { + // Treat all RPC payment access errors the same, whether payment is actually required or not + THROW_WALLET_EXCEPTION_IF(error.code == CORE_RPC_ERROR_CODE_INVALID_CLIENT, tools::error::deprecated_rpc_access, method); THROW_WALLET_EXCEPTION_IF(error.code, tools::error::wallet_coded_rpc_error, method, error.code, get_rpc_server_error_message(error.code)); THROW_WALLET_EXCEPTION_IF(!r, tools::error::no_connection_to_daemon, method); // empty string -> not connection THROW_WALLET_EXCEPTION_IF(status.empty(), tools::error::no_connection_to_daemon, method); THROW_WALLET_EXCEPTION_IF(status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, method); - THROW_WALLET_EXCEPTION_IF(status == CORE_RPC_STATUS_PAYMENT_REQUIRED, tools::error::payment_required, method); + THROW_WALLET_EXCEPTION_IF(status == CORE_RPC_STATUS_PAYMENT_REQUIRED, tools::error::deprecated_rpc_access, method); + // Deprecated RPC payment access endpoints would set status to "Client signature does not verify for <method>" + THROW_WALLET_EXCEPTION_IF(status.compare(0, 16, "Client signature") == 0, tools::error::deprecated_rpc_access, method); } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 16e898ad8..554a766bf 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -63,21 +63,19 @@ #include "serialization/crypto.h" #include "serialization/string.h" #include "serialization/pair.h" +#include "serialization/tuple.h" #include "serialization/containers.h" #include "wallet_errors.h" #include "common/password.h" #include "node_rpc_proxy.h" #include "message_store.h" -#include "wallet_light_rpc.h" -#include "wallet_rpc_helpers.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" #define THROW_ON_RPC_RESPONSE_ERROR(r, error, res, method, ...) \ do { \ - handle_payment_changes(res, std::integral_constant<bool, HasCredits<decltype(res)>::Has>()); \ throw_on_rpc_response_error(r, error, res.status, method); \ THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, ## __VA_ARGS__); \ } while(0) @@ -100,6 +98,7 @@ namespace tools uint64_t pick(); gamma_picker(const std::vector<uint64_t> &rct_offsets); gamma_picker(const std::vector<uint64_t> &rct_offsets, double shape, double scale); + uint64_t get_num_rct_outs() const { return num_rct_outputs; } private: struct gamma_engine @@ -137,16 +136,12 @@ private: public: // Full wallet callbacks virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} + virtual void on_reorg(uint64_t height, uint64_t blocks_detached, size_t transfers_detached) {} virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {} virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {} virtual boost::optional<epee::wipeable_string> on_get_password(const char *reason) { return boost::none; } - // Light wallet callbacks - virtual void on_lw_new_block(uint64_t height) {} - virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {} - virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {} - virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) {} // Device callbacks virtual void on_device_button_request(uint64_t code) {} virtual void on_device_button_pressed() {} @@ -401,9 +396,13 @@ private: } m_flags; uint64_t m_amount; std::vector<crypto::public_key> m_additional_tx_keys; + uint32_t m_subaddr_index_major; + uint32_t m_subaddr_index_minor; BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) + if (version < 1) + return false; FIELD(m_pubkey) VARINT_FIELD(m_internal_output_index) VARINT_FIELD(m_global_output_index) @@ -411,6 +410,8 @@ private: FIELD(m_flags.flags) VARINT_FIELD(m_amount) FIELD(m_additional_tx_keys) + VARINT_FIELD(m_subaddr_index_major) + VARINT_FIELD(m_subaddr_index_minor) END_SERIALIZE() }; @@ -468,7 +469,7 @@ private: time_t m_sent_time; std::vector<cryptonote::tx_destination_entry> m_dests; crypto::hash m_payment_id; - enum { pending, pending_not_in_pool, failed } m_state; + enum { pending, pending_in_pool, failed } m_state; uint64_t m_timestamp; uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer std::set<uint32_t> m_subaddr_indices; // set of address indices used as inputs in this transfer @@ -667,16 +668,32 @@ private: struct unsigned_tx_set { std::vector<tx_construction_data> txes; - std::pair<size_t, wallet2::transfer_container> transfers; - std::pair<size_t, std::vector<wallet2::exported_transfer_details>> new_transfers; + std::tuple<uint64_t, uint64_t, wallet2::transfer_container> transfers; + std::tuple<uint64_t, uint64_t, std::vector<wallet2::exported_transfer_details>> new_transfers; BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(1) + VERSION_FIELD(2) FIELD(txes) - if (version >= 1) - FIELD(new_transfers) - else - FIELD(transfers) + if (version == 0) + { + std::pair<size_t, wallet2::transfer_container> v0_transfers; + FIELD(v0_transfers); + std::get<0>(transfers) = std::get<0>(v0_transfers); + std::get<1>(transfers) = std::get<0>(v0_transfers) + std::get<1>(v0_transfers).size(); + std::get<2>(transfers) = std::get<1>(v0_transfers); + return true; + } + if (version == 1) + { + std::pair<size_t, std::vector<wallet2::exported_transfer_details>> v1_transfers; + FIELD(v1_transfers); + std::get<0>(new_transfers) = std::get<0>(v1_transfers); + std::get<1>(new_transfers) = std::get<0>(v1_transfers) + std::get<1>(v1_transfers).size(); + std::get<2>(new_transfers) = std::get<1>(v1_transfers); + return true; + } + + FIELD(new_transfers) END_SERIALIZE() }; @@ -793,8 +810,33 @@ private: bool empty() const { return tx_extra_fields.empty() && primary.empty() && additional.empty(); } }; + struct detached_blockchain_data + { + hashchain detached_blockchain; + size_t original_chain_size; + std::unordered_set<crypto::hash> detached_tx_hashes; + std::unordered_map<crypto::hash, std::vector<cryptonote::tx_destination_entry>> detached_confirmed_txs_dests; + }; + + struct process_tx_entry_t + { + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry tx_entry; + cryptonote::transaction tx; + crypto::hash tx_hash; + }; + + struct tx_entry_data + { + std::vector<process_tx_entry_t> tx_entries; + uint64_t lowest_height; + uint64_t highest_height; + + tx_entry_data(): lowest_height((uint64_t)-1), highest_height(0) {} + }; + /*! - * \brief Generates a wallet or restores one. + * \brief Generates a wallet or restores one. Assumes the multisig setup + * has already completed for the provided multisig info. * \param wallet_ Name of wallet file * \param password Password of wallet file * \param multisig_data The multisig restore info and keys @@ -862,7 +904,8 @@ private: * to other participants */ std::string exchange_multisig_keys(const epee::wipeable_string &password, - const std::vector<std::string> &kex_messages); + const std::vector<std::string> &kex_messages, + const bool force_update_use_with_caution = false); /*! * \brief Get initial message to start multisig key exchange (before 'make_multisig()' is called) * \return string to send to other participants @@ -957,14 +1000,6 @@ private: bool get_seed(epee::wipeable_string& electrum_words, const epee::wipeable_string &passphrase = epee::wipeable_string()) const; /*! - * \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned. - */ - bool light_wallet() const { return m_light_wallet; } - void set_light_wallet(bool light_wallet) { m_light_wallet = light_wallet; } - uint64_t get_light_wallet_scanned_block_height() const { return m_light_wallet_scanned_block_height; } - uint64_t get_light_wallet_blockchain_height() const { return m_light_wallet_blockchain_height; } - - /*! * \brief Gets the seed language */ const std::string &get_seed_language() const; @@ -998,7 +1033,7 @@ private: bool is_deprecated() const; void refresh(bool trusted_daemon); void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched); - void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true); + void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true, bool try_incremental = true, uint64_t max_blocks = std::numeric_limits<uint64_t>::max()); bool refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& received_money, bool& ok); void set_refresh_type(RefreshType refresh_type) { m_refresh_type = refresh_type; } @@ -1068,7 +1103,9 @@ private: bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids); std::vector<pending_tx> create_unmixable_sweep_transactions(); void discard_unmixable_outputs(); - bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000); + bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000, bool *wallet_is_outdated = NULL, bool *daemon_is_outdated = NULL); + bool check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated); + bool check_hard_fork_version(cryptonote::network_type nettype, const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; @@ -1077,7 +1114,7 @@ private: void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; - uint64_t get_blockchain_current_height() const { return m_light_wallet_blockchain_height ? m_light_wallet_blockchain_height : m_blockchain.size(); } + uint64_t get_blockchain_current_height() const { return m_blockchain.size(); } void rescan_spent(); void rescan_blockchain(bool hard, bool refresh = true, bool keep_key_images = false); bool is_transfer_unlocked(const transfer_details& td); @@ -1205,12 +1242,19 @@ private: a & m_cold_key_images.parent(); if(ver < 29) return; - a & m_rpc_client_secret_key; + crypto::secret_key dummy_rpc_client_secret_key; // Compatibility for old RPC payment system + a & dummy_rpc_client_secret_key; + if(ver < 30) + { + m_has_ever_refreshed_from_node = false; + return; + } + a & m_has_ever_refreshed_from_node; } BEGIN_SERIALIZE_OBJECT() MAGIC_FIELD("monero wallet cache") - VERSION_FIELD(0) + VERSION_FIELD(1) FIELD(m_blockchain) FIELD(m_transfers) FIELD(m_account_public_address) @@ -1235,7 +1279,14 @@ private: FIELD(m_tx_device) FIELD(m_device_last_key_image_sync) FIELD(m_cold_key_images) - FIELD(m_rpc_client_secret_key) + crypto::secret_key dummy_rpc_client_secret_key; // Compatibility for old RPC payment system + FIELD_N("m_rpc_client_secret_key", dummy_rpc_client_secret_key) + if (version < 1) + { + m_has_ever_refreshed_from_node = false; + return true; + } + FIELD(m_has_ever_refreshed_from_node) END_SERIALIZE() /*! @@ -1313,16 +1364,10 @@ private: inline void set_export_format(const ExportFormat& export_format) { m_export_format = export_format; } bool load_deprecated_formats() const { return m_load_deprecated_formats; } void load_deprecated_formats(bool load) { m_load_deprecated_formats = load; } - bool persistent_rpc_client_id() const { return m_persistent_rpc_client_id; } - void persistent_rpc_client_id(bool persistent) { m_persistent_rpc_client_id = persistent; } - void auto_mine_for_rpc_payment_threshold(float threshold) { m_auto_mine_for_rpc_payment_threshold = threshold; } - float auto_mine_for_rpc_payment_threshold() const { return m_auto_mine_for_rpc_payment_threshold; } - crypto::secret_key get_rpc_client_secret_key() const { return m_rpc_client_secret_key; } - void set_rpc_client_secret_key(const crypto::secret_key &key) { m_rpc_client_secret_key = key; m_node_rpc_proxy.set_client_secret_key(key); } - uint64_t credits_target() const { return m_credits_target; } - void credits_target(uint64_t threshold) { m_credits_target = threshold; } bool is_multisig_enabled() const { return m_enable_multisig; } void enable_multisig(bool enable) { m_enable_multisig = enable; } + bool is_mismatched_daemon_version_allowed() const { return m_allow_mismatched_daemon_version; } + void allow_mismatched_daemon_version(bool allow_mismatch) { m_allow_mismatched_daemon_version = allow_mismatch; } bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress = boost::none); @@ -1339,7 +1384,7 @@ private: std::string get_spend_proof(const crypto::hash &txid, const std::string &message); bool check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str); - void scan_tx(const std::vector<crypto::hash> &txids); + void scan_tx(const std::unordered_set<crypto::hash> &txids); /*! * \brief Generates a proof that proves the reserve of unspent funds @@ -1447,10 +1492,10 @@ private: 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::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> export_outputs(bool all = false) const; - std::string export_outputs_to_str(bool all = false) const; - size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs); - size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs); + std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> export_outputs(bool all = false, uint32_t start = 0, uint32_t count = 0xffffffff) const; + std::string export_outputs_to_str(bool all = false, uint32_t start = 0, uint32_t count = 0xffffffff) const; + size_t import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs); + size_t import_outputs(const std::tuple<uint64_t, uint64_t, 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); @@ -1465,9 +1510,9 @@ private: bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false); crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; - void update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed = false); + void update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed = false, bool try_incremental = false); void process_pool_state(const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs); - void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes); + void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes, bool remove_if_found); std::string encrypt(const char *plaintext, size_t len, const crypto::secret_key &skey, bool authenticated = true) const; std::string encrypt(const epee::span<char> &span, const crypto::secret_key &skey, bool authenticated = true) const; @@ -1502,38 +1547,6 @@ private: std::pair<size_t, uint64_t> estimate_tx_size_and_weight(bool use_rct, int n_inputs, int ring_size, int n_outputs, size_t extra_size); - bool get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie); - bool daemon_requires_payment(); - bool make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance); - bool search_for_rpc_payment(uint64_t credits_target, uint32_t n_threads, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc = NULL, const std::function<void(const std::string&)> &errorfunc = NULL); - template<typename T> void handle_payment_changes(const T &res, std::true_type) { - if (res.status == CORE_RPC_STATUS_OK || res.status == CORE_RPC_STATUS_PAYMENT_REQUIRED) - m_rpc_payment_state.credits = res.credits; - if (res.top_hash != m_rpc_payment_state.top_hash) - { - m_rpc_payment_state.top_hash = res.top_hash; - m_rpc_payment_state.stale = true; - } - } - template<typename T> void handle_payment_changes(const T &res, std::false_type) {} - - // Light wallet specific functions - // fetch unspent outs from lw node and store in m_transfers - void light_wallet_get_unspent_outs(); - // fetch txs and store in m_payments - void light_wallet_get_address_txs(); - // get_address_info - bool light_wallet_get_address_info(tools::COMMAND_RPC_GET_ADDRESS_INFO::response &response); - // Login. new_address is true if address hasn't been used on lw node before. - bool light_wallet_login(bool &new_address); - // Send an import request to lw node. returns info about import fee, address and payment_id - bool light_wallet_import_wallet_request(tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response); - // get random outputs from light wallet server - void light_wallet_get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count); - // Parse rct string - bool light_wallet_parse_rct_str(const std::string& rct_string, const crypto::public_key& tx_pub_key, uint64_t internal_output_index, rct::key& decrypted_mask, rct::key& rct_commit, bool decrypt) const; - // check if key image is ours - bool light_wallet_key_image_is_ours(const crypto::key_image& key_image, const crypto::public_key& tx_public_key, uint64_t out_index); /* * "attributes" are a mechanism to store an arbitrary number of string values @@ -1631,9 +1644,6 @@ private: void set_offline(bool offline = true); bool is_offline() const { return m_offline; } - uint64_t credits() const { return m_rpc_payment_state.credits; } - void credit_report(uint64_t &expected_spent, uint64_t &discrepancy) const { expected_spent = m_rpc_payment_state.expected_spent; discrepancy = m_rpc_payment_state.discrepancy; } - static std::string get_default_daemon_address() { CRITICAL_REGION_LOCAL(default_daemon_address_lock); return default_daemon_address; } private: @@ -1658,18 +1668,24 @@ private: */ bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password); bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional<crypto::chacha_key>& keys_to_encrypt); - void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); + void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL, bool ignore_callbacks = false); bool should_skip_block(const cryptonote::block &b, uint64_t height) const; void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); - void detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); + detached_blockchain_data detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); + void handle_reorg(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const; bool clear(); void clear_soft(bool keep_key_images=false); - void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t ¤t_height); + void pull_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t ¤t_height, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>>& process_pool_txs); void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes); void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false); - void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception); + void pull_and_parse_next_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>>& process_pool_txs, bool &last, bool &error, std::exception_ptr &exception); void process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); + bool accept_pool_tx_for_processing(const crypto::hash &txid); + void process_unconfirmed_transfer(bool incremental, const crypto::hash &txid, wallet2::unconfirmed_transfer_details &tx_details, bool seen_in_pool, std::chrono::system_clock::time_point now, bool refreshed); + void process_pool_info_extent(const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response &res, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed); + void update_pool_state_by_pool_query(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed = false); + void update_pool_state_from_pool_data(bool incremental, const std::vector<crypto::hash> &removed_pool_txids, const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &added_pool_txs, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed); uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers) const; bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height); @@ -1712,6 +1728,9 @@ private: crypto::chacha_key get_ringdb_key(); void setup_keys(const epee::wipeable_string &password); size_t get_transfer_details(const crypto::key_image &ki) const; + tx_entry_data get_tx_entries(const std::unordered_set<crypto::hash> &txids); + void sort_scan_tx_entries(std::vector<process_tx_entry_t> &unsorted_tx_entries); + void process_scan_txs(const tx_entry_data &txs_to_scan, const tx_entry_data &txs_to_reprocess, const std::unordered_set<crypto::hash> &tx_hashes_to_reprocess, detached_blockchain_data &dbd); void register_devices(); hw::device& lookup_device(const std::string & device_descriptor); @@ -1737,9 +1756,6 @@ private: std::string get_rpc_status(const std::string &s) const; void throw_on_rpc_response_error(bool r, const epee::json_rpc::error &error, const std::string &status, const char *method) const; - std::string get_client_signature() const; - void check_rpc_cost(const char *call, uint64_t post_call_credits, uint64_t pre_credits, double expected_cost); - bool should_expand(const cryptonote::subaddress_index &index) const; bool spends_one_of_ours(const cryptonote::transaction &tx) const; @@ -1804,6 +1820,10 @@ private: // If m_refresh_from_block_height is explicitly set to zero we need this to differentiate it from the case that // m_refresh_from_block_height was defaulted to zero.*/ bool m_explicit_refresh_from_block_height; + uint64_t m_pool_info_query_time; + uint64_t m_skip_to_height; + // m_skip_to_height is useful when we don't want to modify the wallet's restore height. + // m_refresh_from_block_height is also a wallet's restore height which should remain constant unless explicitly modified by the user. bool m_confirm_non_default_ring_size; AskPasswordType m_ask_password; uint64_t m_max_reorg_depth; @@ -1824,7 +1844,6 @@ private: bool m_show_wallet_name_when_locked; uint32_t m_inactivity_lock_timeout; BackgroundMiningSetupType m_setup_background_mining; - bool m_persistent_rpc_client_id; float m_auto_mine_for_rpc_payment_threshold; bool m_is_initialized; NodeRPCProxy m_node_rpc_proxy; @@ -1836,28 +1855,12 @@ private: bool m_use_dns; bool m_offline; uint32_t m_rpc_version; - crypto::secret_key m_rpc_client_secret_key; - rpc_payment_state_t m_rpc_payment_state; - uint64_t m_credits_target; bool m_enable_multisig; + bool m_allow_mismatched_daemon_version; // Aux transaction data from device serializable_unordered_map<crypto::hash, std::string> m_tx_device; - // Light wallet - bool m_light_wallet; /* sends view key to daemon for scanning */ - uint64_t m_light_wallet_scanned_block_height; - uint64_t m_light_wallet_blockchain_height; - uint64_t m_light_wallet_per_kb_fee = FEE_PER_KB; - bool m_light_wallet_connected; - uint64_t m_light_wallet_balance; - uint64_t m_light_wallet_unlocked_balance; - // Light wallet info needed to populate m_payment requires 2 separate api calls (get_address_txs and get_unspent_outs) - // We save the info from the first call in m_light_wallet_address_txs for easier lookup. - std::unordered_map<crypto::hash, address_tx> m_light_wallet_address_txs; - // store calculated key image for faster lookup - serializable_unordered_map<crypto::public_key, serializable_map<uint64_t, crypto::key_image> > m_key_image_cache; - std::string m_ring_database; bool m_ring_history_saved; std::unique_ptr<ringdb> m_ringdb; @@ -1883,11 +1886,13 @@ private: ExportFormat m_export_format; bool m_load_deprecated_formats; + bool m_has_ever_refreshed_from_node; + static boost::mutex default_daemon_address_lock; static std::string default_daemon_address; }; } -BOOST_CLASS_VERSION(tools::wallet2, 29) +BOOST_CLASS_VERSION(tools::wallet2, 30) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) @@ -1898,7 +1903,7 @@ BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6) BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 18) BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0) -BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) +BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4) BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) @@ -1908,6 +1913,17 @@ namespace boost { namespace serialization { + template<class Archive, class F, class S, class T> + inline void serialize( + Archive & ar, + std::tuple<F, S, T> & t, + const unsigned int /* file_version */ + ){ + ar & boost::serialization::make_nvp("f", std::get<0>(t)); + ar & boost::serialization::make_nvp("s", std::get<1>(t)); + ar & boost::serialization::make_nvp("t", std::get<2>(t)); + } + template <class Archive> inline typename std::enable_if<!Archive::is_loading::value, void>::type initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) { @@ -2268,7 +2284,17 @@ namespace boost inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver) { a & x.txes; - a & x.transfers; + if (ver == 0) + { + // load old version + std::pair<size_t, tools::wallet2::transfer_container> old_transfers; + a & old_transfers; + std::get<0>(x.transfers) = std::get<0>(old_transfers); + std::get<1>(x.transfers) = std::get<0>(old_transfers) + std::get<1>(old_transfers).size(); + std::get<2>(x.transfers) = std::get<1>(old_transfers); + return; + } + throw std::runtime_error("Boost serialization not supported for newest unsigned_tx_set"); } template <class Archive> diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index ce13fc573..9710f134c 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -76,10 +76,6 @@ namespace wallet_args { return {"wallet-file", wallet_args::tr("Use wallet <arg>"), ""}; } - command_line::arg_descriptor<std::string> arg_rpc_client_secret_key() - { - return {"rpc-client-secret-key", wallet_args::tr("Set RPC client secret key for RPC payments"), ""}; - } command_line::arg_descriptor<std::string> arg_password_file() { return {"password-file", wallet_args::tr("Wallet password file"), ""}; diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index 350fce24e..e5b440fc3 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -36,7 +36,6 @@ namespace wallet_args { command_line::arg_descriptor<std::string> arg_generate_from_json(); command_line::arg_descriptor<std::string> arg_wallet_file(); - command_line::arg_descriptor<std::string> arg_rpc_client_secret_key(); command_line::arg_descriptor<std::string> arg_password_file(); const char* tr(const char* str); diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index df594aa21..6706e77ff 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -91,8 +91,10 @@ namespace tools // is_key_image_spent_error // get_histogram_error // get_output_distribution - // payment_required + // deprecated_rpc_access // wallet_files_doesnt_correspond + // scan_tx_error * + // wont_reprocess_recent_txs_via_untrusted_daemon // // * - class with protected ctor @@ -439,6 +441,16 @@ namespace tools std::string to_string() const { return refresh_error::to_string(); } }; //---------------------------------------------------------------------------------------------------- + struct incorrect_fork_version : public refresh_error + { + explicit incorrect_fork_version(std::string&& loc, const std::string& message) + : refresh_error(std::move(loc), message) + { + } + + std::string to_string() const { return refresh_error::to_string(); } + }; + //---------------------------------------------------------------------------------------------------- struct signature_check_failed : public wallet_logic_error { explicit signature_check_failed(std::string&& loc, const std::string& message) @@ -855,10 +867,11 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- - struct payment_required: public wallet_rpc_error + struct deprecated_rpc_access: public wallet_rpc_error { - explicit payment_required(std::string&& loc, const std::string& request) - : wallet_rpc_error(std::move(loc), "payment required", request) + // The daemon we connected to has enabled the old pay-to-access RPC feature + explicit deprecated_rpc_access(std::string&& loc, const std::string& request) + : wallet_rpc_error(std::move(loc), "daemon requires deprecated RPC payment", request) { } }; @@ -905,6 +918,23 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct scan_tx_error : public wallet_logic_error + { + protected: + explicit scan_tx_error(std::string&& loc, const std::string& message) + : wallet_logic_error(std::move(loc), message) + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct wont_reprocess_recent_txs_via_untrusted_daemon : public scan_tx_error + { + explicit wont_reprocess_recent_txs_via_untrusted_daemon(std::string&& loc) + : scan_tx_error(std::move(loc), "The wallet has already seen 1 or more recent transactions than the scanned tx") + { + } + }; + //---------------------------------------------------------------------------------------------------- #if !defined(_MSC_VER) diff --git a/src/wallet/wallet_light_rpc.h b/src/wallet/wallet_light_rpc.h deleted file mode 100644 index 743a147f6..000000000 --- a/src/wallet/wallet_light_rpc.h +++ /dev/null @@ -1,367 +0,0 @@ -// Copyright (c) 2014-2022, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once -#include "cryptonote_basic/cryptonote_basic.h" -#include "crypto/hash.h" - -namespace tools -{ - //----------------------------------------------- - struct COMMAND_RPC_GET_ADDRESS_TXS - { - struct request_t - { - std::string address; - std::string view_key; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) - KV_SERIALIZE(view_key) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct spent_output { - uint64_t amount; - std::string key_image; - std::string tx_pub_key; - uint64_t out_index; - uint32_t mixin; - - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(key_image) - KV_SERIALIZE(tx_pub_key) - KV_SERIALIZE(out_index) - KV_SERIALIZE(mixin) - END_KV_SERIALIZE_MAP() - }; - - struct transaction - { - uint64_t id; - std::string hash; - uint64_t timestamp; - uint64_t total_received; - uint64_t total_sent; - uint64_t unlock_time; - uint64_t height; - std::list<spent_output> spent_outputs; - std::string payment_id; - bool coinbase; - bool mempool; - uint32_t mixin; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(id) - KV_SERIALIZE(hash) - KV_SERIALIZE(timestamp) - KV_SERIALIZE(total_received) - KV_SERIALIZE(total_sent) - KV_SERIALIZE(unlock_time) - KV_SERIALIZE(height) - KV_SERIALIZE(spent_outputs) - KV_SERIALIZE(payment_id) - KV_SERIALIZE(coinbase) - KV_SERIALIZE(mempool) - KV_SERIALIZE(mixin) - END_KV_SERIALIZE_MAP() - }; - - - struct response_t - { - //std::list<std::string> txs_as_json; - uint64_t total_received; - uint64_t total_received_unlocked = 0; // OpenMonero only - uint64_t scanned_height; - std::vector<transaction> transactions; - uint64_t blockchain_height; - uint64_t scanned_block_height; - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(total_received) - KV_SERIALIZE(total_received_unlocked) - KV_SERIALIZE(scanned_height) - KV_SERIALIZE(transactions) - KV_SERIALIZE(blockchain_height) - KV_SERIALIZE(scanned_block_height) - KV_SERIALIZE(status) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - - //----------------------------------------------- - struct COMMAND_RPC_GET_ADDRESS_INFO - { - struct request_t - { - std::string address; - std::string view_key; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) - KV_SERIALIZE(view_key) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct spent_output - { - uint64_t amount; - std::string key_image; - std::string tx_pub_key; - uint64_t out_index; - uint32_t mixin; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(key_image) - KV_SERIALIZE(tx_pub_key) - KV_SERIALIZE(out_index) - KV_SERIALIZE(mixin) - END_KV_SERIALIZE_MAP() - }; - - struct response_t - { - uint64_t locked_funds; - uint64_t total_received; - uint64_t total_sent; - uint64_t scanned_height; - uint64_t scanned_block_height; - uint64_t start_height; - uint64_t transaction_height; - uint64_t blockchain_height; - std::list<spent_output> spent_outputs; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(locked_funds) - KV_SERIALIZE(total_received) - KV_SERIALIZE(total_sent) - KV_SERIALIZE(scanned_height) - KV_SERIALIZE(scanned_block_height) - KV_SERIALIZE(start_height) - KV_SERIALIZE(transaction_height) - KV_SERIALIZE(blockchain_height) - KV_SERIALIZE(spent_outputs) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - - //----------------------------------------------- - struct COMMAND_RPC_GET_UNSPENT_OUTS - { - struct request_t - { - std::string amount; - std::string address; - std::string view_key; - // OpenMonero specific - uint64_t mixin; - bool use_dust; - std::string dust_threshold; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(address) - KV_SERIALIZE(view_key) - KV_SERIALIZE(mixin) - KV_SERIALIZE(use_dust) - KV_SERIALIZE(dust_threshold) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - - struct output { - uint64_t amount; - std::string public_key; - uint64_t index; - uint64_t global_index; - std::string rct; - std::string tx_hash; - std::string tx_pub_key; - std::string tx_prefix_hash; - std::vector<std::string> spend_key_images; - uint64_t timestamp; - uint64_t height; - - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(public_key) - KV_SERIALIZE(index) - KV_SERIALIZE(global_index) - KV_SERIALIZE(rct) - KV_SERIALIZE(tx_hash) - KV_SERIALIZE(tx_pub_key) - KV_SERIALIZE(tx_prefix_hash) - KV_SERIALIZE(spend_key_images) - KV_SERIALIZE(timestamp) - KV_SERIALIZE(height) - END_KV_SERIALIZE_MAP() - }; - - struct response_t - { - uint64_t amount; - std::list<output> outputs; - uint64_t per_kb_fee; - std::string status; - std::string reason; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(outputs) - KV_SERIALIZE(per_kb_fee) - KV_SERIALIZE(status) - KV_SERIALIZE(reason) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - //----------------------------------------------- - struct COMMAND_RPC_LOGIN - { - struct request_t - { - std::string address; - std::string view_key; - bool create_account; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) - KV_SERIALIZE(view_key) - KV_SERIALIZE(create_account) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct response_t - { - std::string status; - std::string reason; - bool new_address; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) - KV_SERIALIZE(reason) - KV_SERIALIZE(new_address) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - //----------------------------------------------- - struct COMMAND_RPC_IMPORT_WALLET_REQUEST - { - struct request_t - { - std::string address; - std::string view_key; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) - KV_SERIALIZE(view_key) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct response_t - { - std::string payment_id; - uint64_t import_fee; - bool new_request; - bool request_fulfilled; - std::string payment_address; - std::string status; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(payment_id) - KV_SERIALIZE(import_fee) - KV_SERIALIZE(new_request) - KV_SERIALIZE(request_fulfilled) - KV_SERIALIZE(payment_address) - KV_SERIALIZE(status) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - //----------------------------------------------- - struct COMMAND_RPC_GET_RANDOM_OUTS - { - struct request_t - { - std::vector<std::string> amounts; - uint32_t count; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amounts) - KV_SERIALIZE(count) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct output { - std::string public_key; - uint64_t global_index; - std::string rct; // 64+64+64 characters long (<rct commit> + <encrypted mask> + <rct amount>) - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(public_key) - KV_SERIALIZE(global_index) - KV_SERIALIZE(rct) - END_KV_SERIALIZE_MAP() - }; - - struct amount_out { - uint64_t amount; - std::vector<output> outputs; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - KV_SERIALIZE(outputs) - END_KV_SERIALIZE_MAP() - }; - - struct response_t - { - std::vector<amount_out> amount_outs; - std::string Error; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount_outs) - KV_SERIALIZE(Error) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - //----------------------------------------------- -} diff --git a/src/wallet/wallet_rpc_helpers.h b/src/wallet/wallet_rpc_helpers.h deleted file mode 100644 index 93fa6996a..000000000 --- a/src/wallet/wallet_rpc_helpers.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2018-2022, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#pragma once - -#include <limits> -#include <type_traits> - -namespace -{ - // credits to yrp (https://stackoverflow.com/questions/87372/check-if-a-class-has-a-member-function-of-a-given-signature - template <typename T> - struct HasCredits - { - template<typename U, uint64_t (U::*)> struct SFINAE {}; - template<typename U> static char Test(SFINAE<U, &U::credits>*); - template<typename U> static int Test(...); - static const bool Has = sizeof(Test<T>(0)) == sizeof(char); - }; -} - -namespace tools -{ - struct rpc_payment_state_t - { - uint64_t credits; - uint64_t expected_spent; - uint64_t discrepancy; - std::string top_hash; - bool stale; - - rpc_payment_state_t(): credits(0), expected_spent(0), discrepancy(0), stale(true) {} - }; - - static inline void check_rpc_cost(rpc_payment_state_t &rpc_payment_state, const char *call, uint64_t post_call_credits, uint64_t pre_call_credits, double expected_cost) - { - uint64_t expected_credits = (uint64_t)expected_cost; - if (expected_credits == 0) - expected_credits = 1; - - rpc_payment_state.credits = post_call_credits; - rpc_payment_state.expected_spent += expected_credits; - - if (pre_call_credits <= post_call_credits) - return; - - uint64_t cost = pre_call_credits - post_call_credits; - - if (cost == expected_credits) - { - MDEBUG("Call " << call << " cost " << cost << " credits"); - return; - } - MWARNING("Call " << call << " cost " << cost << " credits, expected " << expected_credits); - - if (cost > expected_credits) - { - uint64_t d = cost - expected_credits; - if (rpc_payment_state.discrepancy > std::numeric_limits<uint64_t>::max() - d) - { - MERROR("Integer overflow in credit discrepancy calculation, setting to max"); - rpc_payment_state.discrepancy = std::numeric_limits<uint64_t>::max(); - } - else - { - rpc_payment_state.discrepancy += d; - } - } - } -} diff --git a/src/wallet/wallet_rpc_payments.cpp b/src/wallet/wallet_rpc_payments.cpp deleted file mode 100644 index 61eaa8070..000000000 --- a/src/wallet/wallet_rpc_payments.cpp +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) 2018-2022, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include <boost/optional/optional.hpp> -#include <boost/utility/value_init.hpp> -#include "include_base_utils.h" -#include "cryptonote_config.h" -#include "wallet_rpc_helpers.h" -#include "wallet2.h" -#include "cryptonote_basic/cryptonote_format_utils.h" -#include "rpc/core_rpc_server_commands_defs.h" -#include "rpc/rpc_payment_signature.h" -#include "misc_language.h" -#include "cryptonote_basic/cryptonote_basic_impl.h" -#include "int-util.h" -#include "crypto/crypto.h" -#include "cryptonote_basic/blobdatatype.h" -#include "common/i18n.h" -#include "common/util.h" -#include "common/threadpool.h" - -#undef MONERO_DEFAULT_LOG_CATEGORY -#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2.rpc_payments" - -#define RPC_PAYMENT_POLL_PERIOD 10 /* seconds*/ - -namespace tools -{ -//---------------------------------------------------------------------------------------------------- -std::string wallet2::get_client_signature() const -{ - return cryptonote::make_rpc_payment_signature(m_rpc_client_secret_key); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie) -{ - boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_payment_info(mining, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie); - credits = m_rpc_payment_state.credits; - if (result && *result != CORE_RPC_STATUS_OK) - return false; - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::daemon_requires_payment() -{ - bool payment_required = false; - uint64_t credits, diff, credits_per_hash_found, height, seed_height; - uint32_t cookie; - cryptonote::blobdata blob; - crypto::hash seed_hash, next_seed_hash; - return get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance) -{ - cryptonote::COMMAND_RPC_ACCESS_SUBMIT_NONCE::request req = AUTO_VAL_INIT(req); - cryptonote::COMMAND_RPC_ACCESS_SUBMIT_NONCE::response res = AUTO_VAL_INIT(res); - req.nonce = nonce; - req.cookie = cookie; - m_daemon_rpc_mutex.lock(); - uint64_t pre_call_credits = m_rpc_payment_state.credits; - req.client = get_client_signature(); - epee::json_rpc::error error; - bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_submit_nonce", req, res, error, *m_http_client, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, error, res, "rpc_access_submit_nonce"); - THROW_WALLET_EXCEPTION_IF(res.credits < pre_call_credits, error::wallet_internal_error, "RPC payment did not increase balance"); - if (m_rpc_payment_state.top_hash != res.top_hash) - { - m_rpc_payment_state.top_hash = res.top_hash; - m_rpc_payment_state.stale = true; - } - - m_rpc_payment_state.credits = res.credits; - balance = res.credits; - credits = balance - pre_call_credits; - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::search_for_rpc_payment(uint64_t credits_target, uint32_t n_threads, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc, const std::function<void(const std::string&)> &errorfunc) -{ - bool need_payment = false; - bool payment_required; - uint64_t credits, diff, credits_per_hash_found, height, seed_height; - uint32_t cookie; - unsigned int n_hashes = 0; - cryptonote::blobdata hashing_blob; - crypto::hash seed_hash, next_seed_hash; - try - { - need_payment = get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required && credits < credits_target; - if (!need_payment) - return true; - if (!startfunc(diff, credits_per_hash_found)) - return true; - } - catch (const std::exception &e) { return false; } - - static std::atomic<uint32_t> nonce(0); - while (contfunc(n_hashes)) - { - try - { - need_payment = get_rpc_payment_info(true, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required && credits < credits_target; - if (!need_payment) - return true; - } - catch (const std::exception &e) { return false; } - if (hashing_blob.empty()) - { - MERROR("Bad hashing blob from daemon"); - if (errorfunc) - errorfunc("Bad hashing blob from daemon, trying again"); - epee::misc_utils::sleep_no_w(1000); - continue; - } - - if(n_threads == 0) - n_threads = boost::thread::hardware_concurrency(); - - std::vector<crypto::hash> hash(n_threads); - tools::threadpool& tpool = tools::threadpool::getInstance(); - tools::threadpool::waiter waiter(tpool); - - const uint32_t local_nonce = nonce += n_threads; // wrapping's OK - for (size_t i = 0; i < n_threads; i++) - { - tpool.submit(&waiter, [&, i] { - *(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(local_nonce-i); - const uint8_t major_version = hashing_blob[0]; - if (major_version >= RX_BLOCK_VERSION) - { - const int miners = 1; - crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash[i].data, miners, 0); - } - else - { - int cn_variant = hashing_blob[0] >= 7 ? hashing_blob[0] - 6 : 0; - crypto::cn_slow_hash(hashing_blob.data(), hashing_blob.size(), hash[i], cn_variant, height); - } - }); - } - waiter.wait(); - n_hashes += n_threads; - - for(size_t i=0; i < n_threads; i++) - { - if (cryptonote::check_hash(hash[i], diff)) - { - uint64_t credits, balance; - try - { - make_rpc_payment(local_nonce-i, cookie, credits, balance); - if (credits != credits_per_hash_found) - { - MERROR("Found nonce, but daemon did not credit us with the expected amount"); - if (errorfunc) - errorfunc("Found nonce, but daemon did not credit us with the expected amount"); - return false; - } - MDEBUG("Found nonce " << local_nonce-i << " at diff " << diff << ", gets us " << credits_per_hash_found << ", now " << balance << " credits"); - if (!foundfunc(credits)) - break; - } - catch (const tools::error::wallet_coded_rpc_error &e) - { - MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon"); - if (errorfunc) - errorfunc("Found nonce, but daemon errored out with error " + std::to_string(e.code()) + ": " + e.status() + ", continuing"); - } - catch (const std::exception &e) - { - MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon"); - if (errorfunc) - errorfunc("Found nonce, but daemon errored out with: '" + std::string(e.what()) + "', continuing"); - } - } - } - } - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::check_rpc_cost(const char *call, uint64_t post_call_credits, uint64_t pre_call_credits, double expected_cost) -{ - return tools::check_rpc_cost(m_rpc_payment_state, call, post_call_credits, pre_call_credits, expected_cost); -} -//---------------------------------------------------------------------------------------------------- -} diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 7ec5fc7a1..7c46d9887 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -60,6 +60,7 @@ using namespace epee; #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" #define DEFAULT_AUTO_REFRESH_PERIOD 20 // seconds +#define REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE 256 // just to split refresh in separate calls to play nicer with other threads #define CHECK_MULTISIG_ENABLED() \ do \ @@ -79,6 +80,7 @@ namespace const command_line::arg_descriptor<bool> arg_restricted = {"restricted-rpc", "Restricts to view-only commands", false}; const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"}; const command_line::arg_descriptor<bool> arg_prompt_for_password = {"prompt-for-password", "Prompts for password when not provided", false}; + const command_line::arg_descriptor<bool> arg_no_initial_sync = {"no-initial-sync", "Skips the initial sync before listening for connections", false}; constexpr const char default_rpc_username[] = "monero"; @@ -149,12 +151,17 @@ namespace tools return true; if (boost::posix_time::microsec_clock::universal_time() < m_last_auto_refresh_time + boost::posix_time::seconds(m_auto_refresh_period)) return true; + uint64_t blocks_fetched = 0; try { - if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon()); + bool received_money = false; + if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, blocks_fetched, received_money, true, true, REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE); } catch (const std::exception& ex) { LOG_ERROR("Exception at while refreshing, what=" << ex.what()); } - m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time(); + // if we got the max amount of blocks, do not set the last refresh time, we did only part of the refresh and will + // continue asap, and only set the last refresh time once the refresh is actually finished + if (blocks_fetched < REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE) + m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time(); return true; }, 1000); m_net_server.add_idle_handler([this](){ @@ -397,7 +404,6 @@ namespace tools bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed; entry.txid = string_tools::pod_to_hex(txid); entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id); - entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id); if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) entry.payment_id = entry.payment_id.substr(0,16); entry.height = 0; @@ -577,9 +583,9 @@ namespace tools if (!m_wallet) return not_open(er); try { - if (req.count < 1 || req.count > 64) { + if (req.count < 1 || req.count > 65536) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Count must be between 1 and 64."; + er.message = "Count must be between 1 and 65536."; return false; } @@ -2792,7 +2798,7 @@ namespace tools try { - res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str(req.all)); + res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str(req.all, req.start, req.count)); } catch (const std::exception &e) { @@ -3167,7 +3173,7 @@ namespace tools return false; } - std::vector<crypto::hash> txids; + std::unordered_set<crypto::hash> txids; std::list<std::string>::const_iterator i = req.txids.begin(); while (i != req.txids.end()) { @@ -3180,11 +3186,15 @@ namespace tools } crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_blob.data()); - txids.push_back(txid); + txids.insert(txid); } try { m_wallet->scan_tx(txids); + } catch (const tools::error::wont_reprocess_recent_txs_via_untrusted_daemon &e) { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what() + std::string(". Either connect to a trusted daemon or rescan the chain."); + return false; } catch (const std::exception &e) { handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); return false; @@ -3254,7 +3264,7 @@ namespace tools if (!m_wallet) return not_open(er); cryptonote::COMMAND_RPC_STOP_MINING::request daemon_req; cryptonote::COMMAND_RPC_STOP_MINING::response daemon_res; - bool r = m_wallet->invoke_http_json("/stop_mining", daemon_req, daemon_res); + bool r = m_wallet->invoke_http_json("/stop_mining", daemon_req, daemon_res, std::chrono::seconds(60)); // this waits till stopped, and if randomx has just started initializing its dataset, it might be a while if (!r || daemon_res.status != CORE_RPC_STATUS_OK) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -3530,7 +3540,6 @@ namespace tools cryptonote::print_money(e.tx_amount() + e.fee()) % cryptonote::print_money(e.tx_amount()) % cryptonote::print_money(e.fee())).str(); - er.message = e.what(); } catch (const tools::error::not_enough_outs_to_mix& e) { @@ -4146,13 +4155,6 @@ namespace tools er.message = "This wallet is not multisig"; return false; } - - if (ready) - { - er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; - er.message = "This wallet is multisig, and already finalized"; - return false; - } CHECK_MULTISIG_ENABLED(); if (req.multisig_info.size() + 1 < total) @@ -4164,7 +4166,7 @@ namespace tools try { - res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info); + res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info, req.force_update_use_with_caution); m_wallet->multisig(&ready); if (ready) { @@ -4519,7 +4521,6 @@ public: const auto arg_wallet_file = wallet_args::arg_wallet_file(); const auto arg_from_json = wallet_args::arg_generate_from_json(); - const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key(); const auto arg_password_file = wallet_args::arg_password_file(); const auto wallet_file = command_line::get_arg(vm, arg_wallet_file); @@ -4528,6 +4529,7 @@ public: const auto password_file = command_line::get_arg(vm, arg_password_file); const auto prompt_for_password = command_line::get_arg(vm, arg_prompt_for_password); const auto password_prompt = prompt_for_password ? password_prompter : nullptr; + const auto no_initial_sync = command_line::get_arg(vm, arg_no_initial_sync); if(!wallet_file.empty() && !from_json.empty()) { @@ -4576,17 +4578,6 @@ public: return false; } - if (!command_line::is_arg_defaulted(vm, arg_rpc_client_secret_key)) - { - crypto::secret_key client_secret_key; - if (!epee::string_tools::hex_to_pod(command_line::get_arg(vm, arg_rpc_client_secret_key), client_secret_key)) - { - MERROR(arg_rpc_client_secret_key.name << ": RPC client secret key should be 32 byte in hex format"); - return false; - } - wal->set_rpc_client_secret_key(client_secret_key); - } - bool quit = false; tools::signal_handler::install([&wal, &quit](int) { assert(wal); @@ -4596,7 +4587,8 @@ public: try { - wal->refresh(wal->is_trusted_daemon()); + if (!no_initial_sync) + wal->refresh(wal->is_trusted_daemon()); } catch (const std::exception& e) { @@ -4692,7 +4684,6 @@ int main(int argc, char** argv) { const auto arg_wallet_file = wallet_args::arg_wallet_file(); const auto arg_from_json = wallet_args::arg_generate_from_json(); - const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key(); po::options_description hidden_options("Hidden"); @@ -4706,7 +4697,8 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_from_json); command_line::add_arg(desc_params, arg_wallet_dir); command_line::add_arg(desc_params, arg_prompt_for_password); - command_line::add_arg(desc_params, arg_rpc_client_secret_key); + command_line::add_arg(desc_params, arg_no_initial_sync); + command_line::add_arg(hidden_options, daemonizer::arg_non_interactive); daemonizer::init_options(hidden_options, desc_params); desc_params.add(hidden_options); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 3088fd9c2..282035052 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index ecfc8e673..72719e982 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 25 +#define WALLET_RPC_VERSION_MINOR 26 #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 @@ -1785,9 +1785,13 @@ namespace wallet_rpc struct request_t { bool all; + uint32_t start; + uint32_t count; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(all) + KV_SERIALIZE_OPT(start, 0u) + KV_SERIALIZE_OPT(count, 0xffffffffu) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2531,10 +2535,12 @@ namespace wallet_rpc { std::string password; std::vector<std::string> multisig_info; + bool force_update_use_with_caution; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(password) KV_SERIALIZE(multisig_info) + KV_SERIALIZE_OPT(force_update_use_with_caution, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index 734229380..8e3bdf650 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022, The Monero Project +// Copyright (c) 2014-2023, The Monero Project // // All rights reserved. // |