diff options
Diffstat (limited to 'src')
63 files changed, 1238 insertions, 683 deletions
diff --git a/src/blockchain_db/CMakeLists.txt b/src/blockchain_db/CMakeLists.txt index 6a1a143c1..14ed76295 100644 --- a/src/blockchain_db/CMakeLists.txt +++ b/src/blockchain_db/CMakeLists.txt @@ -33,10 +33,7 @@ set(blockchain_db_sources set(blockchain_db_headers) -set(blockchain_db_private_headers - blockchain_db.h - lmdb/db_lmdb.h - ) +monero_find_all_headers(blockchain_db_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(blockchain_db ${crypto_private_headers}) diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 3522c4a1b..e2ac9df0b 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1045,8 +1045,9 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash, CURSOR(output_txs) CURSOR(output_amounts) - if (tx_output.target.type() != typeid(txout_to_key)) - throw0(DB_ERROR("Wrong output type: expected txout_to_key")); + crypto::public_key output_public_key; + if (!get_output_public_key(tx_output, output_public_key)) + throw0(DB_ERROR("Could not get an output public key from a tx output.")); if (tx_output.amount == 0 && !commitment) throw0(DB_ERROR("RCT output without commitment")); @@ -1074,7 +1075,7 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash, else ok.amount_index = 0; ok.output_id = m_num_outputs; - ok.data.pubkey = boost::get < txout_to_key > (tx_output.target).key; + ok.data.pubkey = output_public_key; ok.data.unlock_time = unlock_time; ok.data.height = m_height; if (tx_output.amount == 0) diff --git a/src/checkpoints/CMakeLists.txt b/src/checkpoints/CMakeLists.txt index 4464f142c..665441f62 100644 --- a/src/checkpoints/CMakeLists.txt +++ b/src/checkpoints/CMakeLists.txt @@ -41,8 +41,7 @@ set(checkpoints_sources set(checkpoints_headers) -set(checkpoints_private_headers - checkpoints.h) +monero_find_all_headers(checkpoints_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(checkpoints ${checkpoints_private_headers}) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index cc0813fa5..b712ee6b1 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -58,36 +58,7 @@ endif() set(common_headers) -set(common_private_headers - apply_permutation.h - base58.h - boost_serialization_helper.h - command_line.h - common_fwd.h - dns_utils.h - download.h - error.h - expect.h - http_connection.h - notify.h - pod-class.h - pruning.h - rpc_client.h - scoped_message_writer.h - unordered_containers_boost_serialization.h - util.h - varint.h - i18n.h - password.h - perf_timer.h - spawn.h - stack_trace.h - threadpool.h - updates.h - aligned.h - timings.h - combinator.h - utf8.h) +monero_find_all_headers(common_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(common ${common_private_headers}) diff --git a/src/common/perf_timer.cpp b/src/common/perf_timer.cpp index 1152bf25a..30164a557 100644 --- a/src/common/perf_timer.cpp +++ b/src/common/perf_timer.cpp @@ -27,7 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <vector> -#include "misc_os_dependent.h" +#include "time_helper.h" #include "perf_timer.h" #undef MONERO_DEFAULT_LOG_CATEGORY diff --git a/src/common/util.cpp b/src/common/util.cpp index d607d8f7f..89dcf4fef 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -59,7 +59,7 @@ #include "include_base_utils.h" #include "file_io_utils.h" #include "wipeable_string.h" -#include "misc_os_dependent.h" +#include "time_helper.h" using namespace epee; #include "crypto/crypto.h" diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index e423e2971..595c7f966 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -58,27 +58,7 @@ include_directories(${RANDOMX_INCLUDE}) set(crypto_headers) -set(crypto_private_headers - blake256.h - chacha.h - crypto-ops.h - crypto.h - generic-ops.h - groestl.h - groestl_tables.h - hash-ops.h - hash.h - hmac-keccak.h - initializer.h - jh.h - keccak.h - oaes_config.h - oaes_lib.h - random.h - skein.h - skein_port.h - CryptonightR_JIT.h - CryptonightR_template.h) +monero_find_all_headers(crypto_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(cncrypto ${crypto_private_headers}) diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 1f46164d7..77a36069a 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -749,4 +749,28 @@ POP_WARNINGS sc_sub(&h, &h, &sum); return sc_isnonzero(&h) == 0; } + + void crypto_ops::derive_view_tag(const key_derivation &derivation, size_t output_index, view_tag &view_tag) { + #pragma pack(push, 1) + struct { + char salt[8]; // view tag domain-separator + key_derivation derivation; + char output_index[(sizeof(size_t) * 8 + 6) / 7]; + } buf; + #pragma pack(pop) + + char *end = buf.output_index; + memcpy(buf.salt, "view_tag", 8); // leave off null terminator + buf.derivation = derivation; + tools::write_varint(end, output_index); + assert(end <= buf.output_index + sizeof buf.output_index); + + // view_tag_full = H[salt|derivation|output_index] + hash view_tag_full; + cn_fast_hash(&buf, end - reinterpret_cast<char *>(&buf), view_tag_full); + + // only need a slice of view_tag_full to realize optimal perf/space efficiency + static_assert(sizeof(crypto::view_tag) <= sizeof(view_tag_full), "view tag should not be larger than hash result"); + memcpy(&view_tag, &view_tag_full, sizeof(crypto::view_tag)); + } } diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 596090329..d8cd6c6a0 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -99,6 +99,10 @@ namespace crypto { ec_scalar c, r; friend class crypto_ops; }; + + POD_CLASS view_tag { + char data; + }; #pragma pack(pop) void hash_to_scalar(const void *data, size_t length, ec_scalar &res); @@ -107,7 +111,7 @@ namespace crypto { static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 && sizeof(public_key) == 32 && sizeof(public_key_memsafe) == 32 && sizeof(secret_key) == 32 && sizeof(key_derivation) == 32 && sizeof(key_image) == 32 && - sizeof(signature) == 64, "Invalid structure size"); + sizeof(signature) == 64 && sizeof(view_tag) == 1, "Invalid structure size"); class crypto_ops { crypto_ops(); @@ -151,6 +155,8 @@ namespace crypto { const public_key *const *, std::size_t, const signature *); friend bool check_ring_signature(const hash &, const key_image &, const public_key *const *, std::size_t, const signature *); + static void derive_view_tag(const key_derivation &, std::size_t, view_tag &); + friend void derive_view_tag(const key_derivation &, std::size_t, view_tag &); }; void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes); @@ -297,6 +303,14 @@ namespace crypto { return check_ring_signature(prefix_hash, image, pubs.data(), pubs.size(), sig); } + /* Derive a 1-byte view tag from the sender-receiver shared secret to reduce scanning time. + * When scanning outputs that were not sent to the user, checking the view tag for a match removes the need to proceed with expensive EC operations + * for an expected 99.6% of outputs (expected false positive rate = 1/2^8 = 1/256 = 0.4% = 100% - 99.6%). + */ + inline void derive_view_tag(const key_derivation &derivation, std::size_t output_index, view_tag &vt) { + crypto_ops::derive_view_tag(derivation, output_index, vt); + } + inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; } @@ -312,6 +326,9 @@ namespace crypto { inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) { epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; } + inline std::ostream &operator <<(std::ostream &o, const crypto::view_tag &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } const extern crypto::public_key null_pkey; const extern crypto::secret_key null_skey; @@ -325,3 +342,4 @@ CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key) CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(public_key_memsafe) CRYPTO_MAKE_HASHABLE(key_image) CRYPTO_MAKE_COMPARABLE(signature) +CRYPTO_MAKE_COMPARABLE(view_tag) diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt index f1ca4de62..1414be1b2 100644 --- a/src/cryptonote_basic/CMakeLists.txt +++ b/src/cryptonote_basic/CMakeLists.txt @@ -56,20 +56,7 @@ set(cryptonote_basic_sources set(cryptonote_basic_headers) -set(cryptonote_basic_private_headers - account.h - account_boost_serialization.h - connection_context.h - cryptonote_basic.h - cryptonote_basic_impl.h - cryptonote_boost_serialization.h - cryptonote_format_utils.h - difficulty.h - hardfork.h - merge_mining.h - miner.h - tx_extra.h - verification_context.h) +monero_find_all_headers(cryptonote_basic_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(cryptonote_basic ${cryptonote_basic_private_headers}) diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index 11e54b479..818999a60 100644 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +// Parts of this file are originally copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net #pragma once #include <unordered_set> @@ -34,7 +35,6 @@ #include <algorithm> #include <boost/date_time/posix_time/posix_time.hpp> #include "net/net_utils_base.h" -#include "copyable_atomic.h" #include "crypto/hash.h" namespace cryptonote @@ -55,6 +55,37 @@ namespace cryptonote state_normal }; + /* + This class was originally from the EPEE module. It is identical in function to std::atomic<uint32_t> except + that it has copy-construction and copy-assignment defined, which means that earliers devs didn't have to write + custom copy-contructors and copy-assingment operators for the outer class, cryptonote_connection_context. + cryptonote_connection_context should probably be refactored because it is both trying to be POD-like while + also (very loosely) controlling access to its atomic members. + */ + class copyable_atomic: public std::atomic<uint32_t> + { + public: + copyable_atomic() + {}; + copyable_atomic(uint32_t value) + { store(value); } + copyable_atomic(const copyable_atomic& a):std::atomic<uint32_t>(a.load()) + {} + copyable_atomic& operator= (const copyable_atomic& a) + { + store(a.load()); + return *this; + } + uint32_t operator++() + { + return std::atomic<uint32_t>::operator++(); + } + uint32_t operator++(int fake) + { + return std::atomic<uint32_t>::operator++(fake); + } + }; + static constexpr int handshake_command() noexcept { return 1001; } bool handshake_complete() const noexcept { return m_state != state_before_handshake; } @@ -67,7 +98,7 @@ namespace cryptonote uint64_t m_remote_blockchain_height; uint64_t m_last_response_height; boost::posix_time::ptime m_last_request_time; - epee::copyable_atomic m_callback_request_count; //in debug purpose: problem with double callback rise + copyable_atomic m_callback_request_count; //in debug purpose: problem with double callback rise crypto::hash m_last_known_hash; uint32_t m_pruning_seed; uint16_t m_rpc_port; @@ -77,8 +108,8 @@ namespace cryptonote int m_expect_response; uint64_t m_expect_height; size_t m_num_requested; - epee::copyable_atomic m_new_stripe_notification{0}; - epee::copyable_atomic m_idle_peer_notification{0}; + copyable_atomic m_new_stripe_notification{0}; + copyable_atomic m_idle_peer_notification{0}; }; inline std::string get_protocol_state_string(cryptonote_connection_context::state s) diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 4ae29281a..127958796 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -74,6 +74,7 @@ namespace cryptonote crypto::hash hash; }; + // outputs <= HF_VERSION_VIEW_TAGS struct txout_to_key { txout_to_key() { } @@ -81,6 +82,19 @@ namespace cryptonote crypto::public_key key; }; + // outputs >= HF_VERSION_VIEW_TAGS + struct txout_to_tagged_key + { + txout_to_tagged_key() { } + txout_to_tagged_key(const crypto::public_key &_key, const crypto::view_tag &_view_tag) : key(_key), view_tag(_view_tag) { } + crypto::public_key key; + crypto::view_tag view_tag; // optimization to reduce scanning time + + BEGIN_SERIALIZE_OBJECT() + FIELD(key) + FIELD(view_tag) + END_SERIALIZE() + }; /* inputs */ @@ -137,7 +151,7 @@ namespace cryptonote typedef boost::variant<txin_gen, txin_to_script, txin_to_scripthash, txin_to_key> txin_v; - typedef boost::variant<txout_to_script, txout_to_scripthash, txout_to_key> txout_target_v; + typedef boost::variant<txout_to_script, txout_to_scripthash, txout_to_key, txout_to_tagged_key> txout_target_v; //typedef std::pair<uint64_t, txout> out_t; struct tx_out @@ -562,6 +576,7 @@ VARIANT_TAG(binary_archive, cryptonote::txin_to_key, 0x2); VARIANT_TAG(binary_archive, cryptonote::txout_to_script, 0x0); VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1); VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2); +VARIANT_TAG(binary_archive, cryptonote::txout_to_tagged_key, 0x3); VARIANT_TAG(binary_archive, cryptonote::transaction, 0xcc); VARIANT_TAG(binary_archive, cryptonote::block, 0xbb); @@ -572,6 +587,7 @@ VARIANT_TAG(json_archive, cryptonote::txin_to_key, "key"); VARIANT_TAG(json_archive, cryptonote::txout_to_script, "script"); VARIANT_TAG(json_archive, cryptonote::txout_to_scripthash, "scripthash"); VARIANT_TAG(json_archive, cryptonote::txout_to_key, "key"); +VARIANT_TAG(json_archive, cryptonote::txout_to_tagged_key, "tagged_key"); VARIANT_TAG(json_archive, cryptonote::transaction, "tx"); VARIANT_TAG(json_archive, cryptonote::block, "block"); @@ -582,5 +598,6 @@ VARIANT_TAG(debug_archive, cryptonote::txin_to_key, "key"); VARIANT_TAG(debug_archive, cryptonote::txout_to_script, "script"); VARIANT_TAG(debug_archive, cryptonote::txout_to_scripthash, "scripthash"); VARIANT_TAG(debug_archive, cryptonote::txout_to_key, "key"); +VARIANT_TAG(debug_archive, cryptonote::txout_to_tagged_key, "tagged_key"); VARIANT_TAG(debug_archive, cryptonote::transaction, "tx"); VARIANT_TAG(debug_archive, cryptonote::block, "block"); diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index d7149b5c9..493c9d91d 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -71,7 +71,11 @@ namespace boost { a & reinterpret_cast<char (&)[sizeof(crypto::key_image)]>(x); } - + template <class Archive> + inline void serialize(Archive &a, crypto::view_tag &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(crypto::view_tag)]>(x); + } template <class Archive> inline void serialize(Archive &a, crypto::signature &x, const boost::serialization::version_type ver) { @@ -103,6 +107,13 @@ namespace boost } template <class Archive> + inline void serialize(Archive &a, cryptonote::txout_to_tagged_key &x, const boost::serialization::version_type ver) + { + a & x.key; + a & x.view_tag; + } + + template <class Archive> inline void serialize(Archive &a, cryptonote::txout_to_scripthash &x, const boost::serialization::version_type ver) { a & x.hash; diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 1bc6b6377..432617a4f 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -155,12 +155,13 @@ namespace cryptonote } for (size_t n = 0; n < tx.rct_signatures.outPk.size(); ++n) { - if (tx.vout[n].target.type() != typeid(txout_to_key)) + crypto::public_key output_public_key; + if (!get_output_public_key(tx.vout[n], output_public_key)) { - LOG_PRINT_L1("Unsupported output type in tx " << get_transaction_hash(tx)); + LOG_PRINT_L1("Failed to get output public key for output " << n << " in tx " << get_transaction_hash(tx)); return false; } - rv.outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key); + rv.outPk[n].dest = rct::pk2rct(output_public_key); } if (!base_only) @@ -852,16 +853,16 @@ namespace cryptonote { for(const tx_out& out: tx.vout) { - CHECK_AND_ASSERT_MES(out.target.type() == typeid(txout_to_key), false, "wrong variant type: " - << out.target.type().name() << ", expected " << typeid(txout_to_key).name() - << ", in transaction id=" << get_transaction_hash(tx)); + crypto::public_key output_public_key; + CHECK_AND_ASSERT_MES(get_output_public_key(out, output_public_key), false, "Failed to get output public key (output type: " + << out.target.type().name() << "), in transaction id=" << get_transaction_hash(tx)); if (tx.version == 1) { CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount output in transaction id=" << get_transaction_hash(tx)); } - if(!check_key(boost::get<txout_to_key>(out.target).key)) + if(!check_key(output_public_key)) return false; } return true; @@ -905,6 +906,30 @@ namespace cryptonote return outputs_amount; } //--------------------------------------------------------------- + bool get_output_public_key(const cryptonote::tx_out& out, crypto::public_key& output_public_key) + { + // before HF_VERSION_VIEW_TAGS, outputs with public keys are of type txout_to_key + // after HF_VERSION_VIEW_TAGS, outputs with public keys are of type txout_to_tagged_key + if (out.target.type() == typeid(txout_to_key)) + output_public_key = boost::get< txout_to_key >(out.target).key; + else if (out.target.type() == typeid(txout_to_tagged_key)) + output_public_key = boost::get< txout_to_tagged_key >(out.target).key; + else + { + LOG_ERROR("Unexpected output target type found: " << out.target.type().name()); + return false; + } + + return true; + } + //--------------------------------------------------------------- + boost::optional<crypto::view_tag> get_output_view_tag(const cryptonote::tx_out& out) + { + return out.target.type() == typeid(txout_to_tagged_key) + ? boost::optional<crypto::view_tag>(boost::get< txout_to_tagged_key >(out.target).view_tag) + : boost::optional<crypto::view_tag>(); + } + //--------------------------------------------------------------- std::string short_hash_str(const crypto::hash& h) { std::string res = string_tools::pod_to_hex(h); @@ -914,45 +939,126 @@ namespace cryptonote return res; } //--------------------------------------------------------------- - bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, size_t output_index) + 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) + { + out.amount = amount; + if (use_view_tags) + { + txout_to_tagged_key ttk; + ttk.key = output_public_key; + ttk.view_tag = view_tag; + out.target = ttk; + } + else + { + txout_to_key tk; + tk.key = output_public_key; + out.target = tk; + } + } + //--------------------------------------------------------------- + bool check_output_types(const transaction& tx, const uint8_t hf_version) + { + for (const auto &o: tx.vout) + { + if (hf_version > HF_VERSION_VIEW_TAGS) + { + // from v15, require outputs have view tags + CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_tagged_key), false, "wrong variant type: " + << o.target.type().name() << ", expected txout_to_tagged_key in transaction id=" << get_transaction_hash(tx)); + } + else if (hf_version < HF_VERSION_VIEW_TAGS) + { + // require outputs to be of type txout_to_key + CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong variant type: " + << o.target.type().name() << ", expected txout_to_key in transaction id=" << get_transaction_hash(tx)); + } + else //(hf_version == HF_VERSION_VIEW_TAGS) + { + // require outputs be of type txout_to_key OR txout_to_tagged_key + // to allow grace period before requiring all to be txout_to_tagged_key + CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key) || o.target.type() == typeid(txout_to_tagged_key), false, "wrong variant type: " + << o.target.type().name() << ", expected txout_to_key or txout_to_tagged_key in transaction id=" << get_transaction_hash(tx)); + + // require all outputs in a tx be of the same type + CHECK_AND_ASSERT_MES(o.target.type() == tx.vout[0].target.type(), false, "non-matching variant types: " + << o.target.type().name() << " and " << tx.vout[0].target.type().name() << ", " + << "expected matching variant types in transaction id=" << get_transaction_hash(tx)); + } + } + 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) + { + // 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. + if (!view_tag_opt) + return true; + + crypto::view_tag view_tag = *view_tag_opt; + + // If the output's view tag does *not* match the derived view tag, the output should not belong to the account. + // 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); + return view_tag == derived_view_tag; + } + //--------------------------------------------------------------- + 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_pub_keys, size_t output_index, const boost::optional<crypto::view_tag>& view_tag_opt) { crypto::key_derivation derivation; 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; - 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"); - if (pk == out_key.key) - return true; + if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + { + 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"); + if (pk == output_public_key) + return true; + } + // try additional tx pubkeys if available if (!additional_tx_pub_keys.empty()) { CHECK_AND_ASSERT_MES(output_index < additional_tx_pub_keys.size(), false, "wrong number of additional tx pubkeys"); 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"); - 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"); - return pk == out_key.key; + if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + { + 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"); + return pk == output_public_key; + } } return false; } //--------------------------------------------------------------- - boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev) + boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev, const boost::optional<crypto::view_tag>& view_tag_opt) { // try the shared tx pubkey crypto::public_key subaddress_spendkey; - hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey); - auto found = subaddresses.find(subaddress_spendkey); - if (found != subaddresses.end()) - return subaddress_receive_info{ found->second, derivation }; + if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + { + hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey); + auto found = subaddresses.find(subaddress_spendkey); + if (found != subaddresses.end()) + return subaddress_receive_info{ found->second, derivation }; + } + // try additional tx pubkeys if available if (!additional_derivations.empty()) { CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations"); - hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey); - found = subaddresses.find(subaddress_spendkey); - if (found != subaddresses.end()) - return subaddress_receive_info{ found->second, additional_derivations[output_index] }; + if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index)) + { + hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey); + auto found = subaddresses.find(subaddress_spendkey); + if (found != subaddresses.end()) + return subaddress_receive_info{ found->second, additional_derivations[output_index] }; + } } return boost::none; } @@ -973,8 +1079,9 @@ namespace cryptonote size_t i = 0; for(const tx_out& o: tx.vout) { - CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong type id in transaction out" ); - if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, additional_tx_pub_keys, i)) + crypto::public_key output_public_key; + CHECK_AND_ASSERT_MES(get_output_public_key(o, output_public_key), false, "unable to get output public key from transaction out" ); + if(is_out_to_acc(acc, output_public_key, tx_pub_key, additional_tx_pub_keys, i, get_output_view_tag(o))) { outs.push_back(i); money_transfered += o.amount; @@ -1064,6 +1171,69 @@ namespace cryptonote return s; } //--------------------------------------------------------------- + uint64_t round_money_up(uint64_t amount, unsigned significant_digits) + { + // round monetary amount up with the requested amount of significant digits + + CHECK_AND_ASSERT_THROW_MES(significant_digits > 0, "significant_digits must not be 0"); + static_assert(sizeof(unsigned long long) == sizeof(uint64_t), "Unexpected unsigned long long size"); + + // we don't need speed, so we do it via text, as it's easier to get right + char buf[32]; + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)amount); + const size_t len = strlen(buf); + if (len > significant_digits) + { + bool bump = false; + char *ptr; + for (ptr = buf + significant_digits; *ptr; ++ptr) + { + // bump digits by one if the following digits past significant digits were to be 5 or more + if (*ptr != '0') + { + bump = true; + *ptr = '0'; + } + } + ptr = buf + significant_digits; + while (bump && ptr > buf) + { + --ptr; + // bumping a nine overflows + if (*ptr == '9') + *ptr = '0'; + else + { + // bumping another digit means we can stop bumping (no carry) + ++*ptr; + bump = false; + } + } + if (bump) + { + // carry reached the highest digit, we need to add a 1 in front + size_t offset = strlen(buf); + for (size_t i = offset + 1; i > 0; --i) + buf[i] = buf[i - 1]; + buf[0] = '1'; + } + } + char *end = NULL; + errno = 0; + const unsigned long long ull = strtoull(buf, &end, 10); + CHECK_AND_ASSERT_THROW_MES(ull != ULONG_MAX || errno == 0, "Failed to parse rounded amount: " << buf); + CHECK_AND_ASSERT_THROW_MES(ull != 0 || amount == 0, "Overflow in rounding"); + return ull; + } + //--------------------------------------------------------------- + std::string round_money_up(const std::string &s, unsigned significant_digits) + { + uint64_t amount; + CHECK_AND_ASSERT_THROW_MES(parse_amount(amount, s), "Failed to parse amount: " << s); + amount = round_money_up(amount, significant_digits); + return print_money(amount); + } + //--------------------------------------------------------------- crypto::hash get_blob_hash(const blobdata& blob) { crypto::hash h = null_hash; diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 25cd89e99..8f5459ca7 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -89,13 +89,16 @@ namespace cryptonote void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id); bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id); - bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index); + 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 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 { subaddress_index index; crypto::key_derivation derivation; }; - boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev); + boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index, hw::device &hwdev, const boost::optional<crypto::view_tag>& view_tag_opt = boost::optional<crypto::view_tag>()); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, std::vector<size_t>& outs, uint64_t& money_transfered); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered); bool get_tx_fee(const transaction& tx, uint64_t & fee); @@ -126,6 +129,8 @@ namespace cryptonote bool parse_and_validate_block_from_blob(const blobdata_ref& b_blob, block& b, crypto::hash &block_hash); bool get_inputs_money_amount(const transaction& tx, uint64_t& money); uint64_t get_outs_money_amount(const transaction& tx); + bool get_output_public_key(const cryptonote::tx_out& out, crypto::public_key& output_public_key); + boost::optional<crypto::view_tag> get_output_view_tag(const cryptonote::tx_out& out); bool check_inputs_types_supported(const transaction& tx); bool check_outs_valid(const transaction& tx); bool parse_amount(uint64_t& amount, const std::string& str_amount); @@ -144,6 +149,8 @@ namespace cryptonote std::string get_unit(unsigned int decimal_point = -1); std::string print_money(uint64_t amount, unsigned int decimal_point = -1); std::string print_money(const boost::multiprecision::uint128_t &amount, unsigned int decimal_point = -1); + uint64_t round_money_up(uint64_t amount, unsigned significant_digits); + std::string round_money_up(const std::string &amount, unsigned significant_digits); //--------------------------------------------------------------- template<class t_object> bool t_serializable_object_from_blob(t_object& to, const blobdata& b_blob) diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 5aee87897..f2a8e9b79 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -126,6 +126,7 @@ #define COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT 1000 #define COMMAND_RPC_GET_BLOCKS_FAST_MAX_TX_COUNT 20000 +#define MAX_RPC_CONTENT_LENGTH 1048576 // 1 MB #define P2P_LOCAL_WHITE_PEERLIST_LIMIT 1000 #define P2P_LOCAL_GRAY_PEERLIST_LIMIT 5000 @@ -169,6 +170,7 @@ #define HF_VERSION_MIN_MIXIN_4 6 #define HF_VERSION_MIN_MIXIN_6 7 #define HF_VERSION_MIN_MIXIN_10 8 +#define HF_VERSION_MIN_MIXIN_15 15 #define HF_VERSION_ENFORCE_RCT 6 #define HF_VERSION_PER_BYTE_FEE 8 #define HF_VERSION_SMALLER_BP 10 @@ -183,8 +185,11 @@ #define HF_VERSION_CLSAG 13 #define HF_VERSION_DETERMINISTIC_UNLOCK_TIME 13 #define HF_VERSION_BULLETPROOF_PLUS 15 +#define HF_VERSION_VIEW_TAGS 15 +#define HF_VERSION_2021_SCALING 15 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8 +#define CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES 2 #define HASH_OF_HASHES_STEP 512 diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 5f5d88356..e272b94f0 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -35,13 +35,7 @@ set(cryptonote_core_sources set(cryptonote_core_headers) -set(cryptonote_core_private_headers - blockchain_storage_boost_serialization.h - blockchain.h - cryptonote_core.h - tx_pool.h - tx_sanity_check.h - cryptonote_tx_utils.h) +monero_find_all_headers(cryptonote_core_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(cryptonote_core ${cryptonote_core_private_headers}) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 4eaca039c..5b7b4353d 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1341,6 +1341,7 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: // one input, of type txin_gen, with height set to the block's height // correct miner tx unlock time // a non-overflowing tx amount (dubious necessity on this check) +// valid output types bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, uint8_t hf_version) { LOG_PRINT_L3("Blockchain::" << __func__); @@ -1369,6 +1370,8 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, return false; } + CHECK_AND_ASSERT_MES(check_output_types(b.miner_tx, hf_version), false, "miner transaction has invalid output type(s) in block " << get_block_hash(b)); + return true; } //------------------------------------------------------------------ @@ -3044,12 +3047,14 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context // from v4, forbid invalid pubkeys if (hf_version >= 4) { for (const auto &o: tx.vout) { - if (o.target.type() == typeid(txout_to_key)) { - const txout_to_key& out_to_key = boost::get<txout_to_key>(o.target); - if (!crypto::check_key(out_to_key.key)) { - tvc.m_invalid_output = true; - return false; - } + crypto::public_key output_public_key; + if (!get_output_public_key(o, output_public_key)) { + tvc.m_invalid_output = true; + return false; + } + if (!crypto::check_key(output_public_key)) { + tvc.m_invalid_output = true; + return false; } } } @@ -3166,6 +3171,13 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } + // from v15, require view tags on outputs + if (!check_output_types(tx, hf_version)) + { + tvc.m_invalid_output = true; + return false; + } + return true; } //------------------------------------------------------------------ @@ -3309,7 +3321,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, size_t n_unmixable = 0, n_mixable = 0; size_t min_actual_mixin = std::numeric_limits<size_t>::max(); size_t max_actual_mixin = 0; - const size_t min_mixin = hf_version >= HF_VERSION_MIN_MIXIN_10 ? 10 : hf_version >= HF_VERSION_MIN_MIXIN_6 ? 6 : hf_version >= HF_VERSION_MIN_MIXIN_4 ? 4 : 2; + const size_t min_mixin = hf_version >= HF_VERSION_MIN_MIXIN_15 ? 15 : hf_version >= HF_VERSION_MIN_MIXIN_10 ? 10 : hf_version >= HF_VERSION_MIN_MIXIN_6 ? 6 : hf_version >= HF_VERSION_MIN_MIXIN_4 ? 4 : 2; for (const auto& txin : tx.vin) { // non txin_to_key inputs will be rejected below @@ -3352,14 +3364,11 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } } - if (((hf_version == HF_VERSION_MIN_MIXIN_10 || hf_version == HF_VERSION_MIN_MIXIN_10+1) && min_actual_mixin != 10) || (hf_version >= HF_VERSION_MIN_MIXIN_10+2 && min_actual_mixin > 10)) - { - MERROR_VER("Tx " << get_transaction_hash(tx) << " has invalid ring size (" << (min_actual_mixin + 1) << "), it should be 11"); - tvc.m_low_mixin = true; - return false; - } - - if (min_actual_mixin < min_mixin) + // The only circumstance where ring sizes less than expected are + // allowed is when spending unmixable non-RCT outputs in the chain. + // Caveat: at HF_VERSION_MIN_MIXIN_15, temporarily allow ring sizes + // of 11 to allow a grace period in the transition to larger ring size. + if (min_actual_mixin < min_mixin && !(hf_version == HF_VERSION_MIN_MIXIN_15 && min_actual_mixin == 10)) { if (n_unmixable == 0) { @@ -3373,6 +3382,15 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, tvc.m_low_mixin = true; return false; } + } else if ((hf_version > HF_VERSION_MIN_MIXIN_15 && min_actual_mixin > 15) + || (hf_version == HF_VERSION_MIN_MIXIN_15 && min_actual_mixin != 15 && min_actual_mixin != 10) // grace period to allow either 15 or 10 + || (hf_version < HF_VERSION_MIN_MIXIN_15 && hf_version >= HF_VERSION_MIN_MIXIN_10+2 && min_actual_mixin > 10) + || ((hf_version == HF_VERSION_MIN_MIXIN_10 || hf_version == HF_VERSION_MIN_MIXIN_10+1) && min_actual_mixin != 10) + ) + { + MERROR_VER("Tx " << get_transaction_hash(tx) << " has invalid ring size (" << (min_actual_mixin + 1) << "), it should be " << (min_mixin + 1)); + tvc.m_low_mixin = true; + return false; } // min/max tx version based on HF, and we accept v1 txes if having a non mixable @@ -3710,11 +3728,24 @@ uint64_t Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_b if (version >= HF_VERSION_PER_BYTE_FEE) { lo = mul128(block_reward, DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT, &hi); - div128_64(hi, lo, min_block_weight, &hi, &lo, NULL, NULL); div128_64(hi, lo, median_block_weight, &hi, &lo, NULL, NULL); - assert(hi == 0); - lo /= 5; - return lo; + if (version >= HF_VERSION_2021_SCALING) + { + // min_fee_per_byte = round_up( 0.95 * block_reward * ref_weight / (fee_median^2) ) + // note: since hardfork HF_VERSION_2021_SCALING, fee_median (a.k.a. median_block_weight) equals effective long term median + div128_64(hi, lo, median_block_weight, &hi, &lo, NULL, NULL); + assert(hi == 0); + lo -= lo / 20; + return lo; + } + else + { + // min_fee_per_byte = 0.2 * block_reward * ref_weight / (min_penalty_free_zone * fee_median) + div128_64(hi, lo, min_block_weight, &hi, &lo, NULL, NULL); + assert(hi == 0); + lo /= 5; + return lo; + } } const uint64_t fee_base = version >= 5 ? DYNAMIC_FEE_PER_KB_BASE_FEE_V5 : DYNAMIC_FEE_PER_KB_BASE_FEE; @@ -3787,6 +3818,81 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const } //------------------------------------------------------------------ +void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, uint64_t base_reward, uint64_t Mnw, uint64_t Mlw, std::vector<uint64_t> &fees) const +{ + // variable names and calculations as per https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf + // from (earlier than) this fork, the base fee is per byte + const uint64_t Mfw = std::min(Mnw, Mlw); + + // 3 kB divided by something ? It's going to be either 0 or *very* quantized, so fold it into integer steps below + //const uint64_t Brlw = DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / Mfw; + + // constant.... equal to 0, unless floating point, so fold it into integer steps below + //const uint64_t Br = DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 + + //const uint64_t Fl = base_reward * Brlw / Mfw; fold Brlw from above + const uint64_t Fl = base_reward * /*Brlw*/ DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / (Mfw * Mfw); + + // fold Fl into this for better precision (and to match the test cases in the PDF) + // const uint64_t Fn = 4 * Fl; + const uint64_t Fn = 4 * base_reward * /*Brlw*/ DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / (Mfw * Mfw); + + // const uint64_t Fm = 16 * base_reward * Br / Mfw; fold Br from above + const uint64_t Fm = 16 * base_reward * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / (CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 * Mfw); + + // const uint64_t Fp = 2 * base_reward / Mnw; + + // fold Br from above, move 4Fm in the max to decrease quantization effect + //const uint64_t Fh = 4 * Fm * std::max<uint64_t>(1, Mfw / (32 * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT * Mnw / CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5)); + const uint64_t Fh = std::max<uint64_t>(4 * Fm, 4 * Fm * Mfw / (32 * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT * Mnw / CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5)); + + fees.resize(4); + fees[0] = cryptonote::round_money_up(Fl, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES); + fees[1] = cryptonote::round_money_up(Fn, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES); + fees[2] = cryptonote::round_money_up(Fm, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES); + fees[3] = cryptonote::round_money_up(Fh, CRYPTONOTE_SCALING_2021_FEE_ROUNDING_PLACES); +} + +void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees) const +{ + const uint8_t version = get_current_hard_fork_version(); + const uint64_t db_height = m_db->height(); + + // we want Mlw = median of max((min(Mbw, 1.7 * Ml), Zm), Ml / 1.7) + // Mbw: block weight for the last 99990 blocks, 0 for the next 10 + // Ml: penalty free zone (dynamic), aka long_term_median, aka median of max((min(Mb, 1.7 * Ml), Zm), Ml / 1.7) + // Zm: 300000 (minimum penalty free zone) + // + // So we copy the current rolling median state, add 10 (grace_blocks) zeroes to it, and get back Mlw + + 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); + + // 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."); + std::vector<uint64_t> weights; + get_last_n_blocks_weights(weights, 100 - grace_blocks); + weights.reserve(100); + for (size_t i = 0; i < grace_blocks; ++i) + weights.push_back(0); + const uint64_t Msw_effective_short_term_median = std::max(epee::misc_utils::median(weights), Mlw_penalty_free_zone_for_wallet); + + const uint64_t Mnw = std::min(Msw_effective_short_term_median, 50 * Mlw_penalty_free_zone_for_wallet); + + uint64_t already_generated_coins = db_height ? m_db->get_block_already_generated_coins(db_height - 1) : 0; + uint64_t base_reward; + if (!get_block_reward(m_current_block_cumul_weight_limit / 2, 1, already_generated_coins, base_reward, version)) + { + MERROR("Failed to determine block reward, using placeholder " << print_money(BLOCK_REWARD_OVERESTIMATE) << " as a high bound"); + base_reward = BLOCK_REWARD_OVERESTIMATE; + } + + get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, base_reward, Mnw, Mlw_penalty_free_zone_for_wallet, fees); +} + +//------------------------------------------------------------------ uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const { const uint8_t version = get_current_hard_fork_version(); @@ -3798,6 +3904,13 @@ uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW) grace_blocks = CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1; + if (version >= HF_VERSION_2021_SCALING) + { + std::vector<uint64_t> fees; + get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, fees); + return fees[0]; + } + const uint64_t min_block_weight = get_min_block_weight(version); std::vector<uint64_t> weights; get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW - grace_blocks); @@ -3882,9 +3995,11 @@ bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, cons } // The original code includes a check for the output corresponding to this input - // to be a txout_to_key. This is removed, as the database does not store this info, - // but only txout_to_key outputs are stored in the DB in the first place, done in - // Blockchain*::add_output + // to be a txout_to_key. This is removed, as the database does not store this info. + // Only txout_to_key (and since HF_VERSION_VIEW_TAGS, txout_to_tagged_key) + // outputs are stored in the DB in the first place, done in Blockchain*::add_output. + // Additional type checks on outputs were also added via cryptonote::check_output_types + // and cryptonote::get_output_public_key (see Blockchain::check_tx_outputs). m_output_keys.push_back(rct::ctkey({rct::pk2rct(pubkey), commitment})); return true; @@ -4472,6 +4587,7 @@ bool Blockchain::check_blockchain_pruning() return m_db->check_pruning(); } //------------------------------------------------------------------ +// returns min(Mb, 1.7*Ml) as per https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf from HF_VERSION_LONG_TERM_BLOCK_WEIGHT uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) const { PERF_TIMER(get_next_long_term_block_weight); @@ -4486,7 +4602,18 @@ uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) cons uint64_t long_term_median = get_long_term_block_weight_median(db_height - nblocks, nblocks); uint64_t long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); - uint64_t short_term_constraint = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 2 / 5; + uint64_t short_term_constraint; + if (hf_version >= HF_VERSION_2021_SCALING) + { + // long_term_block_weight = block_weight bounded to range [long-term-median/1.7, long-term-median*1.7] + block_weight = std::max<uint64_t>(block_weight, long_term_effective_median_block_weight * 10 / 17); + short_term_constraint = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 7 / 10; + } + else + { + // long_term_block_weight = block_weight bounded to range [0, long-term-median*1.4] + short_term_constraint = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 2 / 5; + } uint64_t long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint); return long_term_block_weight; @@ -4528,7 +4655,11 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); - uint64_t short_term_constraint = m_long_term_effective_median_block_weight + m_long_term_effective_median_block_weight * 2 / 5; + uint64_t short_term_constraint = m_long_term_effective_median_block_weight; + if (hf_version >= HF_VERSION_2021_SCALING) + short_term_constraint += m_long_term_effective_median_block_weight * 7 / 10; + else + short_term_constraint += m_long_term_effective_median_block_weight * 2 / 5; uint64_t long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint); if (db_height == 1) @@ -4547,7 +4678,19 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); uint64_t short_term_median = epee::misc_utils::median(weights); - uint64_t effective_median_block_weight = std::min<uint64_t>(std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, short_term_median), CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight); + uint64_t effective_median_block_weight; + if (hf_version >= HF_VERSION_2021_SCALING) + { + // effective median = short_term_median bounded to range [long_term_median, 50*long_term_median], but it can't be smaller than the + // minimum penalty free zone (a.k.a. 'full reward zone') + effective_median_block_weight = std::min<uint64_t>(std::max<uint64_t>(m_long_term_effective_median_block_weight, short_term_median), CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight); + } + else + { + // effective median = short_term_median bounded to range [0, 50*long_term_median], but it can't be smaller than the + // minimum penalty free zone (a.k.a. 'full reward zone') + effective_median_block_weight = std::min<uint64_t>(std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, short_term_median), CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight); + } m_current_block_cumul_weight_median = effective_median_block_weight; } diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 5460d7761..7a94f6358 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -648,6 +648,22 @@ namespace cryptonote * @return the fee estimate */ uint64_t get_dynamic_base_fee_estimate(uint64_t grace_blocks) const; + void get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, uint64_t base_reward, uint64_t Mnw, uint64_t Mlw, std::vector<uint64_t> &fees) const; + + /** + * @brief get four levels of dynamic per byte fee estimate for the next few blocks + * + * The dynamic fee is based on the block weight in a past window, and + * the current block reward. It is expressed per byte, and is based on + * https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf + * This function calculates an estimate for a dynamic fee which will be + * valid for the next grace_blocks + * + * @param grace_blocks number of blocks we want the fee to be valid for + * + * @return the fee estimates (4 of them) + */ + void get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees) const; /** * @brief validate a transaction's fee diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 2d2b1cbcf..a78f5d673 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1177,7 +1177,8 @@ namespace cryptonote return false; } - if (!check_tx_inputs_ring_members_diff(tx)) + const uint8_t hf_version = m_blockchain_storage.get_current_hard_fork_version(); + if (!check_tx_inputs_ring_members_diff(tx, hf_version)) { MERROR_VER("tx uses duplicate ring members"); return false; @@ -1189,6 +1190,12 @@ namespace cryptonote return false; } + if (!check_output_types(tx, hf_version)) + { + MERROR_VER("tx does not use valid output type(s)"); + return false; + } + return true; } //----------------------------------------------------------------------------------------------- @@ -1295,10 +1302,9 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::check_tx_inputs_ring_members_diff(const transaction& tx) const + bool core::check_tx_inputs_ring_members_diff(const transaction& tx, const uint8_t hf_version) const { - const uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); - if (version >= 6) + if (hf_version >= 6) { for(const auto& in: tx.vin) { diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 2e5248c5d..0b36730b6 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -1012,10 +1012,11 @@ namespace cryptonote * @brief verify that each ring uses distinct members * * @param tx the transaction to check + * @param hf_version the hard fork version rules to use * * @return false if any ring uses duplicate members, true otherwise */ - bool check_tx_inputs_ring_members_diff(const transaction& tx) const; + bool check_tx_inputs_ring_members_diff(const transaction& tx, const uint8_t hf_version) const; /** * @brief verify that each input key image in a transaction is in diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 76192be5d..1d2024a05 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -149,12 +149,17 @@ namespace cryptonote r = crypto::derive_public_key(derivation, no, miner_address.m_spend_public_key, out_eph_public_key); CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << no << ", "<< miner_address.m_spend_public_key << ")"); - txout_to_key tk; - tk.key = out_eph_public_key; + uint64_t amount = out_amounts[no]; + summary_amounts += amount; + + bool use_view_tags = hard_fork_version >= HF_VERSION_VIEW_TAGS; + crypto::view_tag view_tag; + if (use_view_tags) + crypto::derive_view_tag(derivation, no, view_tag); tx_out out; - summary_amounts += out.amount = out_amounts[no]; - out.target = tk; + cryptonote::set_tx_out(amount, out_eph_public_key, use_view_tags, view_tag, out); + tx.vout.push_back(out); } @@ -198,7 +203,7 @@ namespace cryptonote return addr.m_view_public_key; } //--------------------------------------------------------------- - bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool shuffle_outs) + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool shuffle_outs, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); @@ -406,17 +411,16 @@ namespace cryptonote { CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount); crypto::public_key out_eph_public_key; + crypto::view_tag view_tag; hwdev.generate_output_ephemeral_keys(tx.version,sender_account_keys, txkey_pub, tx_key, dst_entr, change_addr, output_index, need_additional_txkeys, additional_tx_keys, - additional_tx_public_keys, amount_keys, out_eph_public_key); + additional_tx_public_keys, amount_keys, out_eph_public_key, + use_view_tags, view_tag); tx_out out; - out.amount = dst_entr.amount; - txout_to_key tk; - tk.key = out_eph_public_key; - out.target = tk; + cryptonote::set_tx_out(dst_entr.amount, out_eph_public_key, use_view_tags, view_tag, out); tx.vout.push_back(out); output_index++; summary_outs_money += dst_entr.amount; @@ -546,7 +550,9 @@ namespace cryptonote } for (size_t i = 0; i < tx.vout.size(); ++i) { - destinations.push_back(rct::pk2rct(boost::get<txout_to_key>(tx.vout[i].target).key)); + crypto::public_key output_public_key; + get_output_public_key(tx.vout[i], output_public_key); + destinations.push_back(rct::pk2rct(output_public_key)); outamounts.push_back(tx.vout[i].amount); amount_out += tx.vout[i].amount; } @@ -607,7 +613,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout) + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); hwdev.open_tx(tx_key); @@ -627,7 +633,8 @@ namespace cryptonote } } - bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, msout); + bool shuffle_outs = true; + bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, msout, shuffle_outs, use_view_tags); hwdev.close_tx(); return r; } catch(...) { @@ -643,7 +650,7 @@ namespace cryptonote crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; - return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}, NULL); + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}, NULL, false); } //--------------------------------------------------------------- bool generate_genesis_block( diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index be6a6d946..f4ffb98ff 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -119,21 +119,23 @@ namespace cryptonote //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr); bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool shuffle_outs = true); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL); + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool shuffle_outs = true, bool use_view_tags = false); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool use_view_tags = false); bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) ; + crypto::public_key &out_eph_public_key, + const bool use_view_tags, crypto::view_tag &view_tag) ; bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) ; + crypto::public_key &out_eph_public_key, + const bool use_view_tags, crypto::view_tag &view_tag) ; bool generate_genesis_block( block& bl diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp b/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp index 4c21de7a5..92ee08054 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp +++ b/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp @@ -43,7 +43,6 @@ #include <boost/date_time/posix_time/posix_time.hpp> #include <boost/thread/thread.hpp> #include "misc_language.h" -#include "pragma_comp_defs.h" #include <algorithm> diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index b81d7c857..455f72472 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -37,30 +37,7 @@ set(daemon_sources set(daemon_headers) -set(daemon_private_headers - command_parser_executor.h - command_server.h - core.h - daemon.h - executor.h - p2p.h - protocol.h - rpc.h - rpc_command_executor.h - - # cryptonote_protocol - ../cryptonote_protocol/cryptonote_protocol_defs.h - ../cryptonote_protocol/cryptonote_protocol_handler.h - ../cryptonote_protocol/cryptonote_protocol_handler.inl - ../cryptonote_protocol/cryptonote_protocol_handler_common.h - - # p2p - ../p2p/net_node.h - ../p2p/net_node_common.h - ../p2p/net_peerlist.h - ../p2p/net_peerlist_boost_serialization.h - ../p2p/p2p_protocol_defs.h - ../p2p/stdafx.h) +monero_find_all_headers(daemon_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(daemon ${daemon_private_headers}) diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index ac95c3afc..cd9e6544e 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -40,7 +40,6 @@ #include "daemon/rpc_command_executor.h" #include "common/common_fwd.h" -#include "net/net_fwd.h" #include "rpc/core_rpc_server.h" namespace daemonize { diff --git a/src/daemon/command_server.h b/src/daemon/command_server.h index 113f99cbc..babb8e85a 100644 --- a/src/daemon/command_server.h +++ b/src/daemon/command_server.h @@ -43,7 +43,6 @@ Passing RPC commands: #include "common/common_fwd.h" #include "console_handler.h" #include "daemon/command_parser_executor.h" -#include "net/net_fwd.h" namespace daemonize { diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 8cbb59716..ebd7eda85 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -43,7 +43,6 @@ #include "common/common_fwd.h" #include "common/rpc_client.h" #include "cryptonote_basic/cryptonote_basic.h" -#include "net/net_fwd.h" #include "rpc/core_rpc_server.h" #undef MONERO_DEFAULT_LOG_CATEGORY diff --git a/src/device/device.hpp b/src/device/device.hpp index ba47115e7..eca91006f 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -222,7 +222,8 @@ namespace hw { const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) = 0; + crypto::public_key &out_eph_public_key, + const bool use_view_tags, crypto::view_tag &view_tag) = 0; virtual bool mlsag_prehash(const std::string &blob, size_t inputs_size, size_t outputs_size, const rct::keyV &hashes, const rct::ctkeyV &outPk, rct::key &prehash) = 0; virtual bool mlsag_prepare(const rct::key &H, const rct::key &xx, rct::key &a, rct::key &aG, rct::key &aHP, rct::key &rvII) = 0; diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp index e7b452d40..d70ece229 100644 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -263,6 +263,11 @@ namespace hw { return true; } + bool device_default::derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag) { + crypto::derive_view_tag(derivation, output_index, view_tag); + return true; + } + bool device_default::conceal_derivation(crypto::key_derivation &derivation, const crypto::public_key &tx_pub_key, const std::vector<crypto::public_key> &additional_tx_pub_keys, const crypto::key_derivation &main_derivation, const std::vector<crypto::key_derivation> &additional_derivations){ return true; } @@ -291,7 +296,8 @@ namespace hw { const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, - std::vector<rct::key> &amount_keys, crypto::public_key &out_eph_public_key) { + std::vector<rct::key> &amount_keys, crypto::public_key &out_eph_public_key, + const bool use_view_tags, crypto::view_tag &view_tag) { crypto::key_derivation derivation; @@ -331,6 +337,12 @@ namespace hw { derivation_to_scalar(derivation, output_index, scalar1); amount_keys.push_back(rct::sk2rct(scalar1)); } + + if (use_view_tags) + { + derive_view_tag(derivation, output_index, view_tag); + } + r = derive_public_key(derivation, output_index, dst_entr.addr.m_spend_public_key, out_eph_public_key); CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to derive_public_key(" << derivation << ", " << output_index << ", "<< dst_entr.addr.m_spend_public_key << ")"); diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp index 60d2ba203..7d3543652 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -101,6 +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); /* ======================================================================= */ @@ -126,7 +127,8 @@ namespace hw { const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) override; + crypto::public_key &out_eph_public_key, + bool use_view_tags, crypto::view_tag &view_tag) override; bool mlsag_prehash(const std::string &blob, size_t inputs_size, size_t outputs_size, const rct::keyV &hashes, const rct::ctkeyV &outPk, rct::key &prehash) override; bool mlsag_prepare(const rct::key &H, const rct::key &xx, rct::key &a, rct::key &aG, rct::key &aHP, rct::key &rvII) override; diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 378d9f533..51e65dfa5 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -524,6 +524,7 @@ namespace hw { static const std::vector<hw::io::hid_conn_params> known_devices { {0x2c97, 0x0001, 0, 0xffa0}, {0x2c97, 0x0004, 0, 0xffa0}, + {0x2c97, 0x0005, 0, 0xffa0}, }; bool device_ledger::connect(void) { @@ -1527,7 +1528,8 @@ namespace hw { const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) { + crypto::public_key &out_eph_public_key, + bool use_view_tags, crypto::view_tag &view_tag) { AUTO_LOCK_CMD(); #ifdef DEBUG_HWDEVICE @@ -1541,6 +1543,8 @@ namespace hw { const boost::optional<cryptonote::account_public_address> change_addr_x = change_addr; const size_t output_index_x = output_index; const bool need_additional_txkeys_x = need_additional_txkeys; + const 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) { @@ -1568,7 +1572,7 @@ namespace hw { log_hexbuffer("generate_output_ephemeral_keys: [[IN]] additional_tx_keys[oi]", additional_tx_keys_x[output_index].data, 32); } this->controle_device->generate_output_ephemeral_keys(tx_version_x, sender_account_keys_x, txkey_pub_x, tx_key_x, dst_entr_x, change_addr_x, output_index_x, need_additional_txkeys_x, additional_tx_keys_x, - additional_tx_public_keys_x, amount_keys_x, out_eph_public_key_x); + 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); } diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index 06521a56f..074bfaa8d 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -273,7 +273,8 @@ namespace hw { const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, std::vector<crypto::public_key> &additional_tx_public_keys, std::vector<rct::key> &amount_keys, - crypto::public_key &out_eph_public_key) override; + crypto::public_key &out_eph_public_key, + const bool use_view_tags, crypto::view_tag &view_tag) override; bool mlsag_prehash(const std::string &blob, size_t inputs_size, size_t outputs_size, const rct::keyV &hashes, const rct::ctkeyV &outPk, rct::key &prehash) override; bool mlsag_prepare(const rct::key &H, const rct::key &xx, rct::key &a, rct::key &aG, rct::key &aHP, rct::key &rvII) override; diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp index 31a242604..a400e82c7 100644 --- a/src/device_trezor/trezor/protocol.cpp +++ b/src/device_trezor/trezor/protocol.cpp @@ -154,8 +154,7 @@ namespace ki { res.emplace_back(); auto & cres = res.back(); - - cres.set_out_key(key_to_string(boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key)); + cres.set_out_key(key_to_string(td.get_public_key())); cres.set_tx_pub_key(key_to_string(tx_pub_key)); cres.set_internal_output_index(td.m_internal_output_index); cres.set_sub_addr_major(td.m_subaddr_index.major); diff --git a/src/hardforks/CMakeLists.txt b/src/hardforks/CMakeLists.txt index 8ce6de021..81a3d694b 100644 --- a/src/hardforks/CMakeLists.txt +++ b/src/hardforks/CMakeLists.txt @@ -29,8 +29,7 @@ set(hardforks_sources hardforks.cpp) -set(hardforks_headers - hardforks.h) +monero_find_all_headers(hardforks_headers "${CMAKE_CURRENT_SOURCE_DIR}") set(hardforks_private_headers) diff --git a/src/lmdb/CMakeLists.txt b/src/lmdb/CMakeLists.txt index e0a3de147..a26c48ad5 100644 --- a/src/lmdb/CMakeLists.txt +++ b/src/lmdb/CMakeLists.txt @@ -27,7 +27,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(lmdb_sources database.cpp error.cpp table.cpp value_stream.cpp) -set(lmdb_headers database.h error.h key_stream.h table.h transaction.h util.h value_stream.h) +monero_find_all_headers(lmdb_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_add_library(lmdb_lib ${lmdb_sources} ${lmdb_headers}) target_link_libraries(lmdb_lib common ${LMDB_LIBRARY}) diff --git a/src/mnemonics/CMakeLists.txt b/src/mnemonics/CMakeLists.txt index d8d1072a7..738633ef5 100644 --- a/src/mnemonics/CMakeLists.txt +++ b/src/mnemonics/CMakeLists.txt @@ -31,23 +31,7 @@ set(mnemonics_sources set(mnemonics_headers) -set(mnemonics_private_headers - electrum-words.h - chinese_simplified.h - english.h - dutch.h - french.h - german.h - italian.h - japanese.h - language_base.h - english_old.h - portuguese.h - russian.h - singleton.h - spanish.h - esperanto.h - lojban.h) +monero_find_all_headers(mnemonics_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(mnemonics ${mnemonics_private_headers}) diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt index 71b5c8e9b..294a1721f 100644 --- a/src/multisig/CMakeLists.txt +++ b/src/multisig/CMakeLists.txt @@ -34,10 +34,7 @@ set(multisig_sources set(multisig_headers) -set(multisig_private_headers - multisig.h - multisig_account.h - multisig_kex_msg.h) +monero_find_all_headers(multisig_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(multisig ${multisig_private_headers}) diff --git a/src/multisig/multisig_account.cpp b/src/multisig/multisig_account.cpp index b7298c4b6..8bd97cf21 100644 --- a/src/multisig/multisig_account.cpp +++ b/src/multisig/multisig_account.cpp @@ -73,7 +73,7 @@ namespace multisig const crypto::public_key &multisig_pubkey, const crypto::public_key &common_pubkey, const std::uint32_t kex_rounds_complete, - kex_origins_map_t kex_origins_map, + multisig_keyset_map_memsafe_t kex_origins_map, std::string next_round_kex_message) : m_base_privkey{base_privkey}, m_base_common_privkey{base_common_privkey}, @@ -89,6 +89,20 @@ namespace multisig CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey), "Failed to derive public key"); set_multisig_config(threshold, std::move(signers)); + + // kex rounds should not exceed post-kex verification round + const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(m_signers.size(), m_threshold)}; + CHECK_AND_ASSERT_THROW_MES(m_kex_rounds_complete <= kex_rounds_required + 1, + "multisig account: tried to reconstruct account, but kex rounds complete counter is invalid."); + + // once an account is done with kex, the 'next kex msg' is always the post-kex verification message + // i.e. the multisig account pubkey signed by the signer's privkey AND the common pubkey + if (main_kex_rounds_done()) + { + m_next_round_kex_message = multisig_kex_msg{kex_rounds_required + 1, + m_base_privkey, + std::vector<crypto::public_key>{m_multisig_pubkey, m_common_pubkey}}.get_msg(); + } } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL @@ -100,14 +114,24 @@ namespace multisig //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL //---------------------------------------------------------------------------------------------------------------------- - bool multisig_account::multisig_is_ready() const + bool multisig_account::main_kex_rounds_done() const { if (account_is_active()) - return multisig_kex_rounds_required(m_signers.size(), m_threshold) == m_kex_rounds_complete; + return m_kex_rounds_complete >= multisig_kex_rounds_required(m_signers.size(), m_threshold); + else + return false; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + 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; else return false; } - //---------------------------------------------------------------------------------------------------------------------- + //---------------------------------------------------------------------------------------------------------------------- // multisig_account: INTERNAL //---------------------------------------------------------------------------------------------------------------------- void multisig_account::set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers) @@ -119,10 +143,6 @@ namespace multisig for (auto signer_it = signers.begin(); signer_it != signers.end(); ++signer_it) { - // signers should all be unique - CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signer_it, *signer_it) == signer_it, - "multisig account: tried to set signers, but found a duplicate signer unexpectedly."); - // signer pubkeys must be in main subgroup, and not identity CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(*signer_it)) && !(*signer_it == rct::rct2pk(rct::identity())), "multisig account: tried to set signers, but a signer pubkey is invalid."); @@ -133,12 +153,11 @@ namespace multisig "multisig account: tried to set signers, but did not find the account's base pubkey in signer list."); // sort signers - std::sort(signers.begin(), signers.end(), - [](const crypto::public_key &key1, const crypto::public_key &key2) -> bool - { - return memcmp(&key1, &key2, sizeof(crypto::public_key)) < 0; - } - ); + std::sort(signers.begin(), signers.end()); + + // signers should all be unique + CHECK_AND_ASSERT_THROW_MES(std::adjacent_find(signers.begin(), signers.end()) == signers.end(), + "multisig account: tried to set signers, but there are duplicate signers unexpectedly."); // set m_threshold = threshold; diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h index b01ae6c88..bb853246a 100644 --- a/src/multisig/multisig_account.h +++ b/src/multisig/multisig_account.h @@ -75,12 +75,12 @@ namespace multisig * - ZtM2: https://web.getmonero.org/library/Zero-to-Monero-2-0-0.pdf Ch. 9, especially Section 9.6.3 * - FROST: https://eprint.iacr.org/2018/417 */ + using multisig_keyset_map_memsafe_t = + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>>; + class multisig_account final { public: - //member types - using kex_origins_map_t = std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>>; - //constructors // default constructor multisig_account() = default; @@ -105,7 +105,7 @@ namespace multisig const crypto::public_key &multisig_pubkey, const crypto::public_key &common_pubkey, const std::uint32_t kex_rounds_complete, - kex_origins_map_t kex_origins_map, + multisig_keyset_map_memsafe_t kex_origins_map, std::string next_round_kex_message); // copy constructor: default @@ -137,13 +137,15 @@ namespace multisig // get kex rounds complete std::uint32_t get_kex_rounds_complete() const { return m_kex_rounds_complete; } // get kex keys to origins map - const kex_origins_map_t& get_kex_keys_to_origins_map() const { return m_kex_keys_to_origins_map; } + const multisig_keyset_map_memsafe_t& get_kex_keys_to_origins_map() const { return m_kex_keys_to_origins_map; } // get the kex msg for the next round const std::string& get_next_kex_round_msg() const { return m_next_round_kex_message; } //account status functions // account has been intialized, and the account holder can use the 'common' key bool account_is_active() const; + // account has gone through main kex rounds, only remaining step is to verify all other participants are ready + bool main_kex_rounds_done() const; // account is ready to make multisig signatures bool multisig_is_ready() const; @@ -178,21 +180,21 @@ namespace multisig * - Collect the local signer's shared keys to ignore in incoming messages, build the aggregate ancillary key * if appropriate. * param: expanded_msgs - set of multisig kex messages to process - * param: rounds_required - number of rounds required for kex + * param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round) * outparam: exclude_pubkeys_out - 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. */ void initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, - const std::uint32_t rounds_required, + const std::uint32_t kex_rounds_required, std::vector<crypto::public_key> &exclude_pubkeys_out); /** * brief: finalize_kex_update - Helper for kex_update_impl() - * param: rounds_required - number of rounds required for kex + * param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round) * param: result_keys_to_origins_map - map between keys for the next round and the other participants they correspond to * inoutparam: temp_account_inout - account to perform last update steps on */ - void finalize_kex_update(const std::uint32_t rounds_required, - kex_origins_map_t result_keys_to_origins_map); + void finalize_kex_update(const std::uint32_t kex_rounds_required, + multisig_keyset_map_memsafe_t result_keys_to_origins_map); //member variables private: @@ -226,7 +228,7 @@ namespace multisig std::uint32_t m_kex_rounds_complete{0}; // this account's pubkeys for the in-progress key exchange round // - either DH derivations (intermediate rounds), H(derivation)*G (final round), empty (when kex is done) - kex_origins_map_t m_kex_keys_to_origins_map; + multisig_keyset_map_memsafe_t m_kex_keys_to_origins_map; // the account's message for the in-progress key exchange round std::string m_next_round_kex_message; }; diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp index 0a0ca7bdc..2127ee04a 100644 --- a/src/multisig/multisig_account_kex_impl.cpp +++ b/src/multisig/multisig_account_kex_impl.cpp @@ -57,6 +57,30 @@ namespace multisig /** * INTERNAL * + * brief: check_multisig_config - validate multisig configuration details + * param: round - the round of the message that should be produced + * param: threshold - threshold for multisig (M in M-of-N) + * param: num_signers - number of participants in multisig (N) + */ + //---------------------------------------------------------------------------------------------------------------------- + static void check_multisig_config(const std::uint32_t round, + const std::uint32_t threshold, + const std::uint32_t num_signers) + { + CHECK_AND_ASSERT_THROW_MES(num_signers > 1, "Must be at least one other multisig signer."); + CHECK_AND_ASSERT_THROW_MES(num_signers <= config::MULTISIG_MAX_SIGNERS, + "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange)."); + CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, + "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, + "Trying to process multisig kex for an invalid round."); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * * brief: calculate_multisig_keypair_from_derivation - wrapper on calculate_multisig_keypair() for an input public key * Converts an input public key into a crypto private key (type cast, does not change serialization), * then passes it to get_multisig_blinded_secret_key(). @@ -224,50 +248,23 @@ namespace multisig /** * INTERNAL * - * brief: multisig_kex_make_next_msg - Construct a kex msg for any round > 1 of multisig key construction. + * brief: multisig_kex_make_round_keys - Makes a kex round's keys. * - Involves DH exchanges with pubkeys provided by other participants. * - Conserves mapping [pubkey -> DH derivation] : [origin keys of participants that share this secret with you]. * param: base_privkey - account's base private key, for performing DH exchanges and signing messages - * param: round - the round of the message that should be produced - * param: threshold - threshold for multisig (M in M-of-N) - * param: num_signers - number of participants in multisig (N) * param: pubkey_origins_map - map between pubkeys to produce DH derivations with and identity keys of * participants who will share each derivation with you * outparam: derivation_origins_map_out - map between DH derivations (shared secrets) and identity keys - * - If msg is not for the last round, then these derivations are also stored in the output message - * so they can be sent to other participants, who will make more DH derivations for the next kex round. - * - If msg is for the last round, then these derivations won't be sent to other participants. - * Instead, they are converted to share secrets (i.e. s = H(derivation)) and multiplied by G. - * The keys s*G are sent to other participants in the message, so they can be used to produce the final - * multisig key via generate_multisig_spend_public_key(). - * - The values s are the local account's shares of the final multisig key's private key. The caller can - * compute those values with calculate_multisig_keypair_from_derivation() (or compute them directly). - * return: multisig kex message for the specified round */ //---------------------------------------------------------------------------------------------------------------------- - static multisig_kex_msg multisig_kex_make_next_msg(const crypto::secret_key &base_privkey, - const std::uint32_t round, - const std::uint32_t threshold, - const std::uint32_t num_signers, - const std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &pubkey_origins_map, - std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &derivation_origins_map_out) + static void multisig_kex_make_round_keys(const crypto::secret_key &base_privkey, + multisig_keyset_map_memsafe_t pubkey_origins_map, + multisig_keyset_map_memsafe_t &derivation_origins_map_out) { - CHECK_AND_ASSERT_THROW_MES(num_signers > 1, "Must be at least one other multisig signer."); - CHECK_AND_ASSERT_THROW_MES(num_signers <= config::MULTISIG_MAX_SIGNERS, - "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange)."); - CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, - "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 > 1, "Round for next msg must be > 1."); - CHECK_AND_ASSERT_THROW_MES(round <= multisig_kex_rounds_required(num_signers, threshold), - "Trying to make key exchange message for an invalid round."); - // make shared secrets with input pubkeys - std::vector<crypto::public_key> msg_pubkeys; - msg_pubkeys.reserve(pubkey_origins_map.size()); derivation_origins_map_out.clear(); - for (const auto &pubkey_and_origins : pubkey_origins_map) + for (auto &pubkey_and_origins : pubkey_origins_map) { // D = 8 * k_base * K_pubkey // note: must be mul8 (cofactor), otherwise it is possible to leak to a malicious participant if the local @@ -281,27 +278,29 @@ namespace multisig rct::scalarmultKey(derivation_rct, rct::pk2rct(pubkey_and_origins.first), rct::sk2rct(base_privkey)); rct::scalarmultKey(derivation_rct, derivation_rct, rct::EIGHT); - crypto::public_key_memsafe derivation{rct::rct2pk(derivation_rct)}; - // retain mapping between pubkey's origins and the DH derivation - // note: if msg for last round, then caller must know how to handle these derivations properly - derivation_origins_map_out[derivation] = pubkey_and_origins.second; - - // if the last round, convert derivations to public keys for the output message - if (round == multisig_kex_rounds_required(num_signers, threshold)) - { - // derived_pubkey = H(derivation)*G - crypto::public_key derived_pubkey; - calculate_multisig_keypair_from_derivation(derivation, derived_pubkey); - msg_pubkeys.push_back(derived_pubkey); - } - // otherwise, put derivations in message directly, so other signers can in turn create derivations (shared secrets) - // with them for the next round - else - msg_pubkeys.push_back(derivation); + // note: if working on last kex round, then caller must know how to handle these derivations properly + derivation_origins_map_out[rct::rct2pk(derivation_rct)] = std::move(pubkey_and_origins.second); } + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: check_messages_round - Check that a set of messages have an expected round number. + * param: expanded_msgs - set of multisig kex messages to process + * param: expected_round - round number the kex messages should have + */ + //---------------------------------------------------------------------------------------------------------------------- + static void check_messages_round(const std::vector<multisig_kex_msg> &expanded_msgs, + const std::uint32_t expected_round) + { + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input message expected."); + const std::uint32_t round{expanded_msgs[0].get_round()}; + CHECK_AND_ASSERT_THROW_MES(round == expected_round, "Messages don't have the expected kex round number."); - return multisig_kex_msg{round, base_privkey, std::move(msg_pubkeys)}; + for (const auto &expanded_msg : expanded_msgs) + CHECK_AND_ASSERT_THROW_MES(expanded_msg.get_round() == round, "All messages must have the same kex round number."); } //---------------------------------------------------------------------------------------------------------------------- /** @@ -327,19 +326,19 @@ namespace multisig static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const crypto::public_key &own_pubkey, const std::vector<multisig_kex_msg> &expanded_msgs, const std::vector<crypto::public_key> &exclude_pubkeys, - std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &sanitized_pubkeys_out) + multisig_keyset_map_memsafe_t &sanitized_pubkeys_out) { + // all messages should have the same round (redundant sanity check) CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input message expected."); + const std::uint32_t round{expanded_msgs[0].get_round()}; + check_messages_round(expanded_msgs, round); - std::uint32_t round = expanded_msgs[0].get_round(); sanitized_pubkeys_out.clear(); // get all pubkeys from input messages, add them to pubkey:origins map // - origins = all the signing pubkeys that recommended a given msg pubkey for (const auto &expanded_msg : expanded_msgs) { - CHECK_AND_ASSERT_THROW_MES(expanded_msg.get_round() == round, "All messages must have the same kex round number."); - // ignore messages from self if (expanded_msg.get_signing_pubkey() == own_pubkey) continue; @@ -378,7 +377,7 @@ namespace multisig * * 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: 'signers', 'exclude_pubkeys'. + * - Require uniqueness in: 'exclude_pubkeys'. * - Requires each input pubkey be recommended by 'num_recommendations = expected_round' msg signers. * - For a final multisig key to be truly 'M-of-N', each of the the private key's components must be * shared by (N - M + 1) signers. @@ -388,39 +387,21 @@ namespace multisig * with the local account. * - Requires that 'exclude_pubkeys' has [num_signers - 1 CHOOSE (expected_round - 1)] pubkeys. * - These should be derivations the local account has corresponding to round 'expected_round'. - * param: base_privkey - multisig account's base private key + * param: base_pubkey - multisig account's base public key * param: expected_round - expected kex round of input messages - * param: threshold - threshold for multisig (M in M-of-N) * 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' * return: fully sanitized and validated pubkey:origins map for building the account's next kex round message */ //---------------------------------------------------------------------------------------------------------------------- - static std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> evaluate_multisig_kex_round_msgs( + static multisig_keyset_map_memsafe_t evaluate_multisig_kex_round_msgs( const crypto::public_key &base_pubkey, const std::uint32_t expected_round, - const std::uint32_t threshold, const std::vector<crypto::public_key> &signers, const std::vector<multisig_kex_msg> &expanded_msgs, const std::vector<crypto::public_key> &exclude_pubkeys) { - CHECK_AND_ASSERT_THROW_MES(signers.size() > 1, "Must be at least one other multisig signer."); - CHECK_AND_ASSERT_THROW_MES(signers.size() <= config::MULTISIG_MAX_SIGNERS, - "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange)."); - CHECK_AND_ASSERT_THROW_MES(signers.size() >= threshold, "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(expected_round > 0, "Expected round must be > 0."); - CHECK_AND_ASSERT_THROW_MES(expected_round <= multisig_kex_rounds_required(signers.size(), threshold), - "Expecting key exchange messages for an invalid round."); - - std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> pubkey_origins_map; - - // leave early in the last round of 1-of-N, where all signers share a key so the local signer doesn't care about - // recommendations from other signers - if (threshold == 1 && expected_round == multisig_kex_rounds_required(signers.size(), threshold)) - return pubkey_origins_map; - // exclude_pubkeys should all be unique for (auto it = exclude_pubkeys.begin(); it != exclude_pubkeys.end(); ++it) { @@ -429,7 +410,8 @@ namespace multisig } // sanitize input messages - 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; + const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, 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 << "]"); @@ -486,10 +468,10 @@ namespace multisig // - 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 // have boosted the local signer (implying they didn't have access to the local signer's previous round msg). - std::uint32_t expected_recommendations_others = n_choose_k_f(signers.size() - 2, round - 1); + const std::uint32_t expected_recommendations_others = n_choose_k_f(signers.size() - 2, round - 1); // local: (N - 1) choose (msg_round_num - 1) - std::uint32_t expected_recommendations_self = n_choose_k_f(signers.size() - 1, round - 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 CHECK_AND_ASSERT_THROW_MES(expected_recommendations_self > 0 && expected_recommendations_others > 0, @@ -517,7 +499,60 @@ namespace multisig /** * INTERNAL * - * brief: multisig_kex_process_round - Process kex messages for the active kex round. + * brief: evaluate_multisig_post_kex_round_msgs - Evaluate messages for the post-kex verification round. + * - Sanitizes input msgs. + * - Requires that only one pubkey is recommended. + * - Requires that all signers (other than self) recommend that one pubkey. + * param: base_pubkey - multisig account's base public key + * 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 + * return: sanitized and validated pubkey:origins map + */ + //---------------------------------------------------------------------------------------------------------------------- + static multisig_keyset_map_memsafe_t evaluate_multisig_post_kex_round_msgs( + 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) + { + // 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); + CHECK_AND_ASSERT_THROW_MES(round == expected_round, + "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]"); + + // evaluate pubkeys collected + + // 1) there should only be two pubkeys + CHECK_AND_ASSERT_THROW_MES(pubkey_origins_map.size() == 2, + "Multisig post-kex round messages from other signers did not all contain two pubkeys."); + + // 2) both keys should be recommended by the same set of signers + 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 + auto origins = pubkey_origins_map.begin()->second; + origins.insert(base_pubkey); //add self + + CHECK_AND_ASSERT_THROW_MES(origins.size() == signers.size(), + "Multisig post-kex round message origins don't line up with multisig signer set."); + + for (const crypto::public_key &signer : signers) + { + 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)."); + } + + return pubkey_origins_map; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: multisig_kex_process_round_msgs - Process kex messages for the active kex round. * - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_next_msg(). * - In other words, evaluate the input messages and try to make a message for the next round. * - Note: Must be called on the final round's msgs to evaluate the final key components @@ -536,43 +571,62 @@ namespace multisig * return: multisig kex message for next round, or empty message if 'current_round' is the final round */ //---------------------------------------------------------------------------------------------------------------------- - static multisig_kex_msg multisig_kex_process_round(const crypto::secret_key &base_privkey, + static void multisig_kex_process_round_msgs(const crypto::secret_key &base_privkey, const crypto::public_key &base_pubkey, const std::uint32_t current_round, const std::uint32_t threshold, const std::vector<crypto::public_key> &signers, const std::vector<multisig_kex_msg> &expanded_msgs, const std::vector<crypto::public_key> &exclude_pubkeys, - std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &keys_to_origins_map_out) + multisig_keyset_map_memsafe_t &keys_to_origins_map_out) { - // evaluate messages - std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> evaluated_pubkeys = - evaluate_multisig_kex_round_msgs(base_pubkey, current_round, threshold, signers, expanded_msgs, exclude_pubkeys); + check_multisig_config(current_round, threshold, signers.size()); + const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(signers.size(), threshold)}; - // produce message for next round (if there is one) - if (current_round < multisig_kex_rounds_required(signers.size(), threshold)) + // process messages into a [pubkey : {origins}] map + multisig_keyset_map_memsafe_t evaluated_pubkeys; + + if (threshold == 1 && current_round == kex_rounds_required) { - return multisig_kex_make_next_msg(base_privkey, - current_round + 1, - threshold, - signers.size(), - evaluated_pubkeys, - keys_to_origins_map_out); + // in the last main kex round of 1-of-N, all signers share a key so the local signer doesn't care about evaluating + // recommendations from other signers } - else + else if (current_round <= kex_rounds_required) { - // no more rounds, so collect the key shares recommended by other signers for the final aggregate key - keys_to_origins_map_out.clear(); - keys_to_origins_map_out = std::move(evaluated_pubkeys); + // for normal kex rounds, fully evaluate kex round messages + evaluated_pubkeys = evaluate_multisig_kex_round_msgs(base_pubkey, + current_round, + signers, + expanded_msgs, + exclude_pubkeys); + } + else //(current_round == kex_rounds_required + 1) + { + // for the post-kex verification round, validate the last kex round's messages + evaluated_pubkeys = evaluate_multisig_post_kex_round_msgs(base_pubkey, + current_round, + signers, + expanded_msgs); + } - return multisig_kex_msg{}; + // prepare keys-to-origins map for updating the multisig account + if (current_round < kex_rounds_required) + { + // normal kex round: make new keys + multisig_kex_make_round_keys(base_privkey, std::move(evaluated_pubkeys), keys_to_origins_map_out); + } + else if (current_round >= kex_rounds_required) + { + // last kex round: collect the key shares recommended by other signers for the final aggregate key + // post-kex verification round: save the keys found in input messages + keys_to_origins_map_out = std::move(evaluated_pubkeys); } } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: INTERNAL //---------------------------------------------------------------------------------------------------------------------- void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, - const std::uint32_t rounds_required, + const std::uint32_t kex_rounds_required, std::vector<crypto::public_key> &exclude_pubkeys_out) { if (m_kex_rounds_complete == 0) @@ -605,7 +659,7 @@ namespace multisig "Failed to derive public key"); // if N-of-N, then the base privkey will be used directly to make the account's share of the final key - if (rounds_required == 1) + if (kex_rounds_required == 1) { m_multisig_privkeys.clear(); m_multisig_privkeys.emplace_back(m_base_privkey); @@ -629,13 +683,29 @@ namespace multisig //---------------------------------------------------------------------------------------------------------------------- // multisig_account: INTERNAL //---------------------------------------------------------------------------------------------------------------------- - void multisig_account::finalize_kex_update(const std::uint32_t rounds_required, - std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> result_keys_to_origins_map) + void multisig_account::finalize_kex_update(const std::uint32_t kex_rounds_required, + multisig_keyset_map_memsafe_t result_keys_to_origins_map) { + std::vector<crypto::public_key> next_msg_keys; + // prepare for next round (or complete the multisig account fully) - if (rounds_required == m_kex_rounds_complete + 1) + if (m_kex_rounds_complete == kex_rounds_required) + { + // 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."); + 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."); + + // save keys that should be recommended to other signers + // - for convenience, re-recommend the post-kex verification message once an account is complete + next_msg_keys.reserve(2); + next_msg_keys.push_back(m_multisig_pubkey); + next_msg_keys.push_back(m_common_pubkey); + } + else if (m_kex_rounds_complete + 1 == kex_rounds_required) { - // finished (have set of msgs to complete address) + // finished with main kex rounds (have set of msgs to complete address) // when 'completing the final round', result keys are other signers' shares of the final key std::vector<crypto::public_key> result_keys; @@ -652,8 +722,14 @@ namespace multisig // no longer need the account's pubkeys saved for this round (they were only used to build exclude_pubkeys) // TODO: record [pre-aggregation pubkeys : origins] map for aggregation-style signing m_kex_keys_to_origins_map.clear(); + + // save keys that should be recommended to other signers + // - for post-kex verification, recommend the multisig pubkeys to notify other signers that the local signer is done + next_msg_keys.reserve(2); + next_msg_keys.push_back(m_multisig_pubkey); + next_msg_keys.push_back(m_common_pubkey); } - else if (rounds_required == m_kex_rounds_complete + 2) + else if (m_kex_rounds_complete + 2 == kex_rounds_required) { // one more round (must send/receive one more set of kex msgs) // - at this point, have local signer's pre-aggregation private key shares of the final address @@ -668,6 +744,7 @@ namespace multisig m_multisig_privkeys.reserve(result_keys_to_origins_map.size()); m_kex_keys_to_origins_map.clear(); + next_msg_keys.reserve(result_keys_to_origins_map.size()); for (const auto &derivation_and_origins : result_keys_to_origins_map) { @@ -679,37 +756,59 @@ namespace multisig // save the account's kex key mappings for this round [derived pubkey : other signers who will have the same key] m_kex_keys_to_origins_map[derived_pubkey] = std::move(derivation_and_origins.second); + + // save keys that should be recommended to other signers + // - The keys multisig_key*G are sent to other participants in the message, so they can be used to produce the final + // multisig key via generate_multisig_spend_public_key(). + next_msg_keys.push_back(derived_pubkey); } } - else + else //(m_kex_rounds_complete + 3 <= kex_rounds_required) { // next round is an 'intermediate' key exchange round, so there is nothing special to do here - // save the account's kex keys for this round [DH derivation : other signers who will have the same derivation] + // save keys that should be recommended to other signers + // - Send this round's DH derivations to other participants, who will make more DH derivations for the following round. + next_msg_keys.reserve(result_keys_to_origins_map.size()); + + for (const auto &derivation_and_origins : result_keys_to_origins_map) + next_msg_keys.push_back(derivation_and_origins.first); + + // save the account's kex keys for this round [DH derivation : other signers who should have the same derivation] m_kex_keys_to_origins_map = std::move(result_keys_to_origins_map); } // a full set of msgs has been collected and processed, so the 'round is complete' ++m_kex_rounds_complete; + + // make next round's message (or reproduce the post-kex verification round if kex is complete) + m_next_round_kex_message = multisig_kex_msg{ + (m_kex_rounds_complete > kex_rounds_required ? kex_rounds_required : m_kex_rounds_complete) + 1, + m_base_privkey, + std::move(next_msg_keys)}.get_msg(); } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: INTERNAL //---------------------------------------------------------------------------------------------------------------------- void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs) { - CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "No key exchange messages passed in."); + // check messages are for the expected kex round + check_messages_round(expanded_msgs, m_kex_rounds_complete + 1); + + // check kex round count + const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(m_signers.size(), m_threshold)}; - const std::uint32_t rounds_required = multisig_kex_rounds_required(m_signers.size(), m_threshold); - CHECK_AND_ASSERT_THROW_MES(rounds_required > 0, "Multisig kex rounds required unexpectedly 0."); + CHECK_AND_ASSERT_THROW_MES(kex_rounds_required > 0, "Multisig kex rounds required unexpectedly 0."); + CHECK_AND_ASSERT_THROW_MES(m_kex_rounds_complete < kex_rounds_required + 1, + "Multisig kex has already completed all required rounds (including post-kex verification)."); // initialize account update std::vector<crypto::public_key> exclude_pubkeys; - initialize_kex_update(expanded_msgs, rounds_required, exclude_pubkeys); - - // evaluate messages and get this account's kex msg for the next round - std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> result_keys_to_origins_map; + initialize_kex_update(expanded_msgs, kex_rounds_required, exclude_pubkeys); - m_next_round_kex_message = multisig_kex_process_round( + // process messages into a [pubkey : {origins}] map + multisig_keyset_map_memsafe_t result_keys_to_origins_map; + multisig_kex_process_round_msgs( m_base_privkey, m_base_pubkey, m_kex_rounds_complete + 1, @@ -717,10 +816,10 @@ namespace multisig m_signers, expanded_msgs, exclude_pubkeys, - result_keys_to_origins_map).get_msg(); + result_keys_to_origins_map); // finish account update - finalize_kex_update(rounds_required, std::move(result_keys_to_origins_map)); + finalize_kex_update(kex_rounds_required, std::move(result_keys_to_origins_map)); } //---------------------------------------------------------------------------------------------------------------------- } //namespace multisig diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt index f4c24ab12..a4d31eedf 100644 --- a/src/net/CMakeLists.txt +++ b/src/net/CMakeLists.txt @@ -29,8 +29,7 @@ set(net_sources dandelionpp.cpp error.cpp http.cpp i2p_address.cpp parse.cpp resolve.cpp socks.cpp socks_connect.cpp tor_address.cpp zmq.cpp) -set(net_headers dandelionpp.h error.h http.cpp i2p_address.h parse.h socks.h resolve.h - socks_connect.h tor_address.h zmq.h) +monero_find_all_headers(net_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_add_library(net ${net_sources} ${net_headers}) target_link_libraries(net common epee ${ZMQ_LIB} ${Boost_ASIO_LIBRARY}) diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 5c61ede3f..a3bc3bf24 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -55,7 +55,6 @@ #include "math_helper.h" #include "misc_log_ex.h" #include "p2p_protocol_defs.h" -#include "net/local_ip.h" #include "crypto/crypto.h" #include "storages/levin_abstract_invoke2.h" #include "cryptonote_core/cryptonote_core.h" diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index 2cb85d270..dc480462d 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -31,6 +31,7 @@ #pragma once #include <iosfwd> +#include <iterator> #include <list> #include <string> #include <vector> @@ -46,7 +47,6 @@ #include "crypto/crypto.h" #include "cryptonote_config.h" #include "net/enums.h" -#include "net/local_ip.h" #include "p2p_protocol_defs.h" #include "syncobj.h" @@ -184,6 +184,7 @@ namespace nodetool private: void trim_white_peerlist(); void trim_gray_peerlist(); + static peerlist_entry get_nth_latest_peer(peers_indexed& peerlist, size_t n); friend class boost::serialization::access; epee::critical_section m_peerlist_lock; @@ -214,6 +215,16 @@ namespace nodetool } } //-------------------------------------------------------------------------------------------------- + inline + peerlist_entry peerlist_manager::get_nth_latest_peer(peers_indexed& peerlist, const size_t n) + { + // Is not thread-safe nor does it check bounds. Do this before calling. Indexing starts at 0. + peers_indexed::index<by_time>::type& by_time_index = peerlist.get<by_time>(); + auto by_time_it = --by_time_index.end(); + std::advance(by_time_it, -static_cast<long long>(n)); + return *by_time_it; + } + //-------------------------------------------------------------------------------------------------- inline bool peerlist_manager::merge_peerlist(const std::vector<peerlist_entry>& outer_bs, const std::function<bool(const peerlist_entry&)> &f) { @@ -235,8 +246,7 @@ namespace nodetool if(i >= m_peers_white.size()) return false; - peers_indexed::index<by_time>::type& by_time_index = m_peers_white.get<by_time>(); - p = *epee::misc_utils::move_it_backward(--by_time_index.end(), i); + p = peerlist_manager::get_nth_latest_peer(m_peers_white, i); return true; } //-------------------------------------------------------------------------------------------------- @@ -247,8 +257,7 @@ namespace nodetool if(i >= m_peers_gray.size()) return false; - peers_indexed::index<by_time>::type& by_time_index = m_peers_gray.get<by_time>(); - p = *epee::misc_utils::move_it_backward(--by_time_index.end(), i); + p = peerlist_manager::get_nth_latest_peer(m_peers_gray, i); return true; } //-------------------------------------------------------------------------------------------------- @@ -437,9 +446,7 @@ namespace nodetool } size_t random_index = crypto::rand_idx(m_peers_gray.size()); - - peers_indexed::index<by_time>::type& by_time_index = m_peers_gray.get<by_time>(); - pe = *epee::misc_utils::move_it_backward(--by_time_index.end(), random_index); + pe = peerlist_manager::get_nth_latest_peer(m_peers_gray, random_index); return true; diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index fbeef6e62..8b9cd0f09 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -30,6 +30,7 @@ #pragma once +#include <iomanip> #include <boost/uuid/uuid.hpp> #include <boost/serialization/version.hpp> #include "serialization/keyvalue_serialization.h" diff --git a/src/p2p/stdafx.h b/src/p2p/stdafx.h deleted file mode 100644 index 27157c8cd..000000000 --- a/src/p2p/stdafx.h +++ /dev/null @@ -1,53 +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 "targetver.h" - - -#if !defined(__GNUC__) -#define _CRTDBG_MAP_ALLOC -#include <stdlib.h> -#include <crtdbg.h> -#endif - - - -#include <stdio.h> - - -#define BOOST_FILESYSTEM_VERSION 3 -#define ENABLE_RELEASE_LOGGING -#include "log_opt_defs.h" -#include "misc_log_ex.h" - - - diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt index 1824c189f..d415e6409 100644 --- a/src/ringct/CMakeLists.txt +++ b/src/ringct/CMakeLists.txt @@ -34,12 +34,7 @@ set(ringct_basic_sources bulletproofs.cc bulletproofs_plus.cc) -set(ringct_basic_private_headers - rctOps.h - rctTypes.h - multiexp.h - bulletproofs.h - bulletproofs_plus.h) +monero_find_all_headers(ringct_basic_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(ringct_basic ${crypto_private_headers}) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index d23d5cddb..869040657 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -48,6 +48,7 @@ using namespace epee; #include "cryptonote_basic/merge_mining.h" #include "cryptonote_core/tx_sanity_check.h" #include "misc_language.h" +#include "net/local_ip.h" #include "net/parse.h" #include "storages/http_abstract_invoke.h" #include "crypto/hash.h" @@ -364,6 +365,8 @@ namespace cryptonote std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options) ); + m_net_server.get_config_object().m_max_content_length = MAX_RPC_CONTENT_LENGTH; + if (store_ssl_key && inited) { // new keys were generated, store for next run @@ -2890,7 +2893,17 @@ namespace cryptonote return r; CHECK_PAYMENT(req, res, COST_PER_FEE_ESTIMATE); - res.fee = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks); + + const uint8_t version = m_core.get_blockchain_storage().get_current_hard_fork_version(); + if (version >= HF_VERSION_2021_SCALING) + { + m_core.get_blockchain_storage().get_dynamic_base_fee_estimate_2021_scaling(req.grace_blocks, res.fees); + res.fee = res.fees[0]; + } + else + { + res.fee = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks); + } res.quantization_mask = Blockchain::get_fee_quantization_mask(); res.status = CORE_RPC_STATUS_OK; return true; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 9206b5e21..1be2610ff 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2191,11 +2191,13 @@ namespace cryptonote { uint64_t fee; uint64_t quantization_mask; + std::vector<uint64_t> fees; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(fee) KV_SERIALIZE_OPT(quantization_mask, (uint64_t)1) + KV_SERIALIZE(fees) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/serialization/CMakeLists.txt b/src/serialization/CMakeLists.txt index f1fd9a31c..d3b9e57ca 100644 --- a/src/serialization/CMakeLists.txt +++ b/src/serialization/CMakeLists.txt @@ -31,8 +31,7 @@ set(serialization_sources set(serialization_headers) -set(serialization_private_headers - json_object.h) +monero_find_all_headers(serialization_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(serialization ${serialization_private_headers}) diff --git a/src/serialization/crypto.h b/src/serialization/crypto.h index 7be04c910..c2a4846ee 100644 --- a/src/serialization/crypto.h +++ b/src/serialization/crypto.h @@ -85,6 +85,7 @@ BLOB_SERIALIZER(crypto::secret_key); BLOB_SERIALIZER(crypto::key_derivation); BLOB_SERIALIZER(crypto::key_image); BLOB_SERIALIZER(crypto::signature); +BLOB_SERIALIZER(crypto::view_tag); VARIANT_TAG(debug_archive, crypto::hash, "hash"); VARIANT_TAG(debug_archive, crypto::hash8, "hash8"); VARIANT_TAG(debug_archive, crypto::public_key, "public_key"); @@ -92,4 +93,5 @@ VARIANT_TAG(debug_archive, crypto::secret_key, "secret_key"); VARIANT_TAG(debug_archive, crypto::key_derivation, "key_derivation"); VARIANT_TAG(debug_archive, crypto::key_image, "key_image"); VARIANT_TAG(debug_archive, crypto::signature, "signature"); +VARIANT_TAG(debug_archive, crypto::view_tag, "view_tag"); diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index e98e327e5..8de5860f6 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -563,6 +563,27 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout) GET_FROM_JSON_OBJECT(val, txout.key, key); } +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::txout_to_tagged_key& txout) +{ + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, key, txout.key); + INSERT_INTO_JSON_OBJECT(dest, view_tag, txout.view_tag); + + dest.EndObject(); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_tagged_key& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txout.key, key); + GET_FROM_JSON_OBJECT(val, txout.view_tag, view_tag); +} + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::tx_out& txout) { dest.StartObject(); @@ -578,6 +599,10 @@ void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::t { INSERT_INTO_JSON_OBJECT(dest, to_key, output); } + void operator()(cryptonote::txout_to_tagged_key const& output) const + { + INSERT_INTO_JSON_OBJECT(dest, to_tagged_key, output); + } void operator()(cryptonote::txout_to_script const& output) const { INSERT_INTO_JSON_OBJECT(dest, to_script, output); @@ -616,6 +641,12 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout) fromJsonValue(elem.value, tmpVal); txout.target = std::move(tmpVal); } + else if (elem.name == "to_tagged_key") + { + cryptonote::txout_to_tagged_key tmpVal; + fromJsonValue(elem.value, tmpVal); + txout.target = std::move(tmpVal); + } else if (elem.name == "to_script") { cryptonote::txout_to_script tmpVal; diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h index e7caa8377..968707165 100644 --- a/src/serialization/json_object.h +++ b/src/serialization/json_object.h @@ -230,6 +230,9 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_scripthash& void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::txout_to_key& txout); void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout); +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::txout_to_tagged_key& txout); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_tagged_key& txout); + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::tx_out& txout); void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout); diff --git a/src/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt index efec8a6f0..b659d9f48 100644 --- a/src/simplewallet/CMakeLists.txt +++ b/src/simplewallet/CMakeLists.txt @@ -31,8 +31,7 @@ set(simplewallet_sources set(simplewallet_headers) -set(simplewallet_private_headers - simplewallet.h) +monero_find_all_headers(simplewallet_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") monero_private_headers(simplewallet ${simplewallet_private_headers}) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 38cf6f757..d3e40ab74 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -99,10 +99,6 @@ typedef cryptonote::simple_wallet sw; #define EXTENDED_LOGS_FILE "wallet_details.log" -#define DEFAULT_MIX 10 - -#define MIN_RING_SIZE 11 // Used to inform user about min ring size -- does not track actual protocol - #define OLD_AGE_WARN_THRESHOLD (30 * 86400 / DIFFICULTY_TARGET_V2) // 30 days #define LOCK_IDLE_SCOPE() \ @@ -1091,7 +1087,9 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo auto local_args = args; local_args.erase(local_args.begin()); std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), local_args, threshold); - if (!multisig_extra_info.empty()) + bool ready; + m_wallet->multisig(&ready); + if (!ready) { success_msg_writer() << tr("Another step is needed"); success_msg_writer() << multisig_extra_info; @@ -1152,7 +1150,7 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & return false; } - if (args.size() < 2) + if (args.size() < 1) { PRINT_USAGE(USAGE_EXCHANGE_MULTISIG_KEYS); return false; @@ -1161,7 +1159,9 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & try { std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args); - if (!multisig_extra_info.empty()) + bool ready; + m_wallet->multisig(&ready); + if (!ready) { message_writer() << tr("Another step is needed"); message_writer() << multisig_extra_info; @@ -2472,59 +2472,6 @@ bool simple_wallet::set_store_tx_info(const std::vector<std::string> &args/* = s return true; } -bool simple_wallet::set_default_ring_size(const std::vector<std::string> &args/* = std::vector<std::string>()*/) -{ - if (m_wallet->watch_only()) - { - fail_msg_writer() << tr("wallet is watch-only and cannot transfer"); - return true; - } - try - { - if (strchr(args[1].c_str(), '-')) - { - fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; - return true; - } - uint32_t ring_size = boost::lexical_cast<uint32_t>(args[1]); - if (ring_size < MIN_RING_SIZE && ring_size != 0) - { - fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; - return true; - } - - if (ring_size != 0 && ring_size != DEFAULT_MIX+1) - { - if (m_wallet->use_fork_rules(8, 0)) - { - message_writer() << tr("WARNING: from v8, ring size will be fixed and this setting will be ignored."); - } - else - { - message_writer() << tr("WARNING: this is a non default ring size, which may harm your privacy. Default is recommended."); - } - } - - const auto pwd_container = get_and_verify_password(); - if (pwd_container) - { - m_wallet->default_mixin(ring_size > 0 ? ring_size - 1 : 0); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); - } - return true; - } - catch(const boost::bad_lexical_cast &) - { - fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; - return true; - } - catch(...) - { - fail_msg_writer() << tr("could not change default ring size"); - return true; - } -} - bool simple_wallet::set_default_priority(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { uint32_t priority = 0; @@ -3386,8 +3333,6 @@ simple_wallet::simple_wallet() " Whether to print detailed information about ring members during confirmation.\n " "store-tx-info <1|0>\n " " Whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference.\n " - "default-ring-size <n>\n " - " Set the default ring size (obsolete).\n " "auto-refresh <1|0>\n " " Whether to automatically synchronize new blocks from the daemon.\n " "refresh-type <full|optimize-coinbase|no-coinbase|default>\n " @@ -3896,7 +3841,6 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("always-confirm-transfers", set_always_confirm_transfers, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("print-ring-members", set_print_ring_members, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("store-tx-info", set_store_tx_info, tr("0 or 1")); - CHECK_SIMPLE_VARIABLE("default-ring-size", set_default_ring_size, tr("integer >= ") << MIN_RING_SIZE); CHECK_SIMPLE_VARIABLE("auto-refresh", set_auto_refresh, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("refresh-type", set_refresh_type, tr("full (slowest, no assumptions); optimize-coinbase (fast, assumes the whole coinbase is paid to a single address); no-coinbase (fastest, assumes we receive no coinbase transaction), default (same as optimize-coinbase)")); CHECK_SIMPLE_VARIABLE("priority", set_default_priority, tr("0, 1, 2, 3, or 4, or one of ") << join_priority_strings(", ")); @@ -6539,7 +6483,8 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri priority = m_wallet->adjust_priority(priority); - size_t fake_outs_count = DEFAULT_MIX; + const size_t min_ring_size = m_wallet->get_min_ring_size(); + size_t fake_outs_count = min_ring_size - 1; if(local_args.size() > 0) { size_t ring_size; if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) @@ -6862,7 +6807,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (vin.type() == typeid(txin_to_key)) { const txin_to_key& in_to_key = boost::get<txin_to_key>(vin); - if (in_to_key.key_offsets.size() != DEFAULT_MIX + 1) + if (in_to_key.key_offsets.size() != min_ring_size) default_ring_size = false; } } @@ -7140,7 +7085,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co priority = m_wallet->adjust_priority(priority); - size_t fake_outs_count = DEFAULT_MIX; + size_t fake_outs_count = m_wallet->get_min_ring_size() - 1; if(local_args.size() > 0) { size_t ring_size; if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) @@ -7417,7 +7362,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) priority = m_wallet->adjust_priority(priority); - size_t fake_outs_count = DEFAULT_MIX; + size_t fake_outs_count = m_wallet->get_min_ring_size() - 1; if(local_args.size() > 0) { size_t ring_size; if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) @@ -9820,7 +9765,7 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg { if (info.has_payment_id) { - success_msg_writer() << boost::format(tr("Integrated address: %s, payment ID: %s")) % + success_msg_writer() << boost::format(tr("Standard address: %s, payment ID: %s")) % get_account_address_as_str(m_wallet->nettype(), false, info.address) % epee::string_tools::pod_to_hex(info.payment_id); device_show_integrated(info.payment_id); } diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 473120eac..4c005c53a 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -125,7 +125,6 @@ namespace cryptonote bool set_always_confirm_transfers(const std::vector<std::string> &args = std::vector<std::string>()); bool set_print_ring_members(const std::vector<std::string> &args = std::vector<std::string>()); bool set_store_tx_info(const std::vector<std::string> &args = std::vector<std::string>()); - bool set_default_ring_size(const std::vector<std::string> &args = std::vector<std::string>()); bool set_auto_refresh(const std::vector<std::string> &args = std::vector<std::string>()); bool set_refresh_type(const std::vector<std::string> &args = std::vector<std::string>()); bool set_confirm_missing_payment_id(const std::vector<std::string> &args = std::vector<std::string>()); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 014aed0e7..7cd8656e1 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1743,8 +1743,8 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str m_wallet->use_fork_rules(8, 0), 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_fee_multiplier(m_wallet->adjust_priority(static_cast<uint32_t>(priority))), m_wallet->get_fee_quantization_mask()); } diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 0701e1a0e..c6f81f0e4 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -423,7 +423,6 @@ struct WalletListener /** * @brief Interface for wallet operations. - * TODO: check if /include/IWallet.h is still actual */ struct Wallet { diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 7635c5646..7810abdd2 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -70,6 +70,7 @@ void NodeRPCProxy::invalidate() m_dynamic_base_fee_estimate = 0; m_dynamic_base_fee_estimate_cached_height = 0; m_dynamic_base_fee_estimate_grace_blocks = 0; + m_dynamic_base_fee_estimate_vector.clear(); m_fee_quantization_mask = 1; m_rpc_version = 0; m_target_height = 0; @@ -210,7 +211,7 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, return boost::optional<std::string>(); } -boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) +boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees) { uint64_t height; @@ -238,13 +239,24 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_ m_dynamic_base_fee_estimate = resp_t.fee; m_dynamic_base_fee_estimate_cached_height = height; m_dynamic_base_fee_estimate_grace_blocks = grace_blocks; + m_dynamic_base_fee_estimate_vector = !resp_t.fees.empty() ? std::move(resp_t.fees) : std::vector<uint64_t>{m_dynamic_base_fee_estimate}; m_fee_quantization_mask = resp_t.quantization_mask; } - fee = m_dynamic_base_fee_estimate; + fees = m_dynamic_base_fee_estimate_vector; return boost::optional<std::string>(); } +boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) +{ + std::vector<uint64_t> fees; + auto res = get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, fees); + if (res) + return res; + fee = fees[0]; + return boost::none; +} + boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask) { uint64_t height; diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 20f4263a6..07675cdb0 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -56,6 +56,7 @@ public: boost::optional<std::string> get_adjusted_time(uint64_t &adjusted_time); boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height); 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); @@ -85,6 +86,7 @@ private: uint64_t m_dynamic_base_fee_estimate; uint64_t m_dynamic_base_fee_estimate_cached_height; uint64_t m_dynamic_base_fee_estimate_grace_blocks; + std::vector<uint64_t> m_dynamic_base_fee_estimate_vector; uint64_t m_fee_quantization_mask; uint64_t m_adjusted_time; uint32_t m_rpc_version; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a53a1b615..9be36bc26 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -290,15 +290,15 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file, mms_file = file_path + ".mms"; } -uint64_t calculate_fee(uint64_t fee_per_kb, size_t bytes, uint64_t fee_multiplier) +uint64_t calculate_fee(uint64_t fee_per_kb, size_t bytes) { uint64_t kB = (bytes + 1023) / 1024; - return kB * fee_per_kb * fee_multiplier; + return kB * fee_per_kb; } -uint64_t calculate_fee_from_weight(uint64_t base_fee, uint64_t weight, uint64_t fee_multiplier, uint64_t fee_quantization_mask) +uint64_t calculate_fee_from_weight(uint64_t base_fee, uint64_t weight, uint64_t fee_quantization_mask) { - uint64_t fee = weight * base_fee * fee_multiplier; + uint64_t fee = weight * base_fee; fee = (fee + fee_quantization_mask - 1) / fee_quantization_mask * fee_quantization_mask; return fee; } @@ -781,7 +781,7 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_ } } -size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus) +size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, bool use_view_tags) { size_t size = 0; @@ -821,6 +821,9 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra else size += n_inputs * (64 * (mixin+1) + 32); + if (use_view_tags) + size += n_outputs * sizeof(crypto::view_tag); + // mixRing - not serialized, can be reconstructed /* size += 2 * 32 * (mixin+1) * n_inputs; */ @@ -837,17 +840,17 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra return size; } -size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus) +size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, bool use_view_tags) { if (use_rct) - return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); + return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); else - return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size; + return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size + (use_view_tags ? (n_outputs * sizeof(crypto::view_tag)) : 0); } -uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus) +uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, bool use_view_tags) { - size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); + size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); if (use_rct && (bulletproof || bulletproof_plus) && n_outputs > 2) { const uint64_t bp_base = (32 * ((bulletproof_plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2) @@ -878,12 +881,17 @@ uint8_t get_clsag_fork() return HF_VERSION_CLSAG; } -uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) +uint8_t get_view_tag_fork() +{ + return HF_VERSION_VIEW_TAGS; +} + +uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_quantization_mask) { if (use_per_byte_fee) - return calculate_fee_from_weight(base_fee, cryptonote::get_transaction_weight(tx, blob_size), fee_multiplier, fee_quantization_mask); + return calculate_fee_from_weight(base_fee, cryptonote::get_transaction_weight(tx, blob_size), fee_quantization_mask); else - return calculate_fee(base_fee, blob_size, fee_multiplier); + return calculate_fee(base_fee, blob_size); } bool get_short_payment_id(crypto::hash8 &payment_id8, const tools::wallet2::pending_tx &ptx, hw::device &hwdev) @@ -1765,13 +1773,14 @@ void wallet2::check_acc_out_precomp(const tx_out &o, const crypto::key_derivatio hw::device &hwdev = m_account.get_device(); boost::unique_lock<hw::device> hwdev_lock (hwdev); hwdev.set_mode(hw::device::TRANSACTION_PARSE); - if (o.target.type() != typeid(txout_to_key)) + crypto::public_key output_public_key; + if (!get_output_public_key(o, output_public_key)) { tx_scan_info.error = true; LOG_ERROR("wrong type id in transaction out"); return; } - tx_scan_info.received = is_out_to_acc_precomp(m_subaddresses, boost::get<txout_to_key>(o.target).key, derivation, additional_derivations, i, hwdev); + tx_scan_info.received = is_out_to_acc_precomp(m_subaddresses, output_public_key, derivation, additional_derivations, i, hwdev, get_output_view_tag(o)); if(tx_scan_info.received) { tx_scan_info.money_transfered = o.amount; // may be 0 for ringct outputs @@ -1856,17 +1865,20 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons } } + crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(!get_output_public_key(tx.vout[i], output_public_key), error::wallet_internal_error, "Failed to get output public key"); + if (m_multisig) { - tx_scan_info.in_ephemeral.pub = boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key; + tx_scan_info.in_ephemeral.pub = output_public_key; tx_scan_info.in_ephemeral.sec = crypto::null_skey; tx_scan_info.ki = rct::rct2ki(rct::zero()); } else { - bool r = cryptonote::generate_key_image_helper_precomp(m_account.get_keys(), boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki, m_account.get_device()); + bool r = cryptonote::generate_key_image_helper_precomp(m_account.get_keys(), output_public_key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, + THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != output_public_key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); } @@ -1993,8 +2005,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote int num_vouts_received = 0; tx_pub_key = pub_key_field.pub_key; - tools::threadpool& tpool = tools::threadpool::getInstance(); - tools::threadpool::waiter waiter(tpool); const cryptonote::account_keys& keys = m_account.get_keys(); crypto::key_derivation derivation; @@ -2064,10 +2074,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote // the first one was already checked for (size_t i = 1; i < tx.vout.size(); ++i) { - tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp_once, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, - std::cref(is_out_data_ptr), std::ref(tx_scan_info[i]), std::ref(output_found[i])), true); + check_acc_out_precomp_once(tx.vout[i], derivation, additional_derivations, i, is_out_data_ptr, tx_scan_info[i], output_found[i]); } - THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); // then scan all outputs from 0 hw::device &hwdev = m_account.get_device(); boost::unique_lock<hw::device> hwdev_lock (hwdev); @@ -2087,32 +2095,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } } } - else if (tx.vout.size() > 1 && tools::threadpool::getInstance().get_max_concurrency() > 1 && !is_out_data_ptr) - { - for (size_t i = 0; i < tx.vout.size(); ++i) - { - tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp_once, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, - std::cref(is_out_data_ptr), std::ref(tx_scan_info[i]), std::ref(output_found[i])), true); - } - THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); - - hw::device &hwdev = m_account.get_device(); - boost::unique_lock<hw::device> hwdev_lock (hwdev); - hwdev.set_mode(hw::device::NONE); - for (size_t i = 0; i < tx.vout.size(); ++i) - { - THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); - if (tx_scan_info[i].received) - { - hwdev.conceal_derivation(tx_scan_info[i].received->derivation, tx_pub_key, additional_tx_pub_keys.data, derivation, additional_derivations); - scan_output(tx, miner_tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs, pool); - if (!tx_scan_info[i].error) - { - tx_amounts_individual_outs[tx_scan_info[i].received->index].push_back(tx_scan_info[i].money_transfered); - } - } - } - } else { for (size_t i = 0; i < tx.vout.size(); ++i) @@ -2793,25 +2775,34 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry for (size_t k = 0; k < n_vouts; ++k) { const auto &o = tx.vout[k]; - if (o.target.type() == typeid(cryptonote::txout_to_key)) + crypto::public_key output_public_key; + if (get_output_public_key(o, output_public_key)) { std::vector<crypto::key_derivation> additional_derivations; additional_derivations.reserve(tx_cache_data[txidx].additional.size()); for (const auto &iod: tx_cache_data[txidx].additional) additional_derivations.push_back(iod.derivation); - const auto &key = boost::get<txout_to_key>(o.target).key; for (size_t l = 0; l < tx_cache_data[txidx].primary.size(); ++l) { THROW_WALLET_EXCEPTION_IF(tx_cache_data[txidx].primary[l].received.size() != n_vouts, error::wallet_internal_error, "Unexpected received array size"); - tx_cache_data[txidx].primary[l].received[k] = is_out_to_acc_precomp(m_subaddresses, key, tx_cache_data[txidx].primary[l].derivation, additional_derivations, k, hwdev); + tx_cache_data[txidx].primary[l].received[k] = is_out_to_acc_precomp(m_subaddresses, output_public_key, tx_cache_data[txidx].primary[l].derivation, additional_derivations, k, hwdev, get_output_view_tag(o)); additional_derivations.clear(); } } } }; + struct geniod_params + { + const cryptonote::transaction &tx; + size_t n_outs; + size_t txidx; + }; + std::vector<geniod_params> geniods; + geniods.reserve(num_txes); txidx = 0; + uint8_t hf_version_view_tags = get_view_tag_fork(); for (size_t i = 0; i < blocks.size(); ++i) { if (should_skip_block(parsed_blocks[i].block, start_height + i)) @@ -2825,18 +2816,51 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range"); const cryptonote::transaction& tx = parsed_blocks[i].block.miner_tx; const size_t n_vouts = (m_refresh_type == RefreshType::RefreshOptimizeCoinbase && tx.version < 2) ? 1 : tx.vout.size(); - tpool.submit(&waiter, [&, n_vouts, txidx](){ geniod(tx, n_vouts, txidx); }, true); + if (parsed_blocks[i].block.major_version >= hf_version_view_tags) + geniods.push_back(geniod_params{ tx, n_vouts, txidx }); + else + tpool.submit(&waiter, [&, n_vouts, txidx](){ geniod(tx, n_vouts, txidx); }, true); } ++txidx; for (size_t j = 0; j < parsed_blocks[i].txes.size(); ++j) { THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range"); - tpool.submit(&waiter, [&, i, j, txidx](){ geniod(parsed_blocks[i].txes[j], parsed_blocks[i].txes[j].vout.size(), txidx); }, true); + if (parsed_blocks[i].block.major_version >= hf_version_view_tags) + geniods.push_back(geniod_params{ parsed_blocks[i].txes[j], parsed_blocks[i].txes[j].vout.size(), txidx }); + else + tpool.submit(&waiter, [&, i, j, txidx](){ geniod(parsed_blocks[i].txes[j], parsed_blocks[i].txes[j].vout.size(), txidx); }, true); ++txidx; } } THROW_WALLET_EXCEPTION_IF(txidx != tx_cache_data.size(), error::wallet_internal_error, "txidx did not reach expected value"); + + // View tags significantly speed up the geniod function that determines if an output belongs to the account. + // Because the speedup is so large, the overhead from submitting individual geniods to the thread pool eats into + // the benefit of executing in parallel. So to maximize the benefit from threads when view tags are enabled, + // the wallet starts submitting geniod function calls to the thread pool in batches of size GENIOD_BATCH_SIZE. + if (geniods.size()) + { + size_t GENIOD_BATCH_SIZE = 100; + size_t num_batch_txes = 0; + size_t batch_start = 0; + while (batch_start < geniods.size()) + { + size_t batch_end = std::min(batch_start + GENIOD_BATCH_SIZE, geniods.size()); + THROW_WALLET_EXCEPTION_IF(batch_end < batch_start, error::wallet_internal_error, "Thread batch end overflow"); + tpool.submit(&waiter, [&geniods, &geniod, batch_start, batch_end]() { + for (size_t i = batch_start; i < batch_end; ++i) + { + const geniod_params &gp = geniods[i]; + geniod(gp.tx, gp.n_outs, gp.txidx); + } + }, true); + num_batch_txes += batch_end - batch_start; + batch_start = batch_end; + } + THROW_WALLET_EXCEPTION_IF(num_batch_txes != geniods.size(), error::wallet_internal_error, "txes batched for thread pool did not reach expected value"); + } THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); + hwdev.set_mode(hw::device::NONE); size_t tx_cache_data_offset = 0; @@ -5101,7 +5125,7 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor // reconstruct multisig account crypto::public_key dummy; - multisig::multisig_account::kex_origins_map_t kex_origins_map; + multisig::multisig_keyset_map_memsafe_t kex_origins_map; for (const auto &derivation : m_multisig_derivations) kex_origins_map[derivation]; @@ -5114,7 +5138,7 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor get_account().get_keys().m_multisig_keys, get_account().get_keys().m_view_secret_key, m_account_public_address.m_spend_public_key, - dummy, //common pubkey: not used + m_account_public_address.m_view_public_key, m_multisig_rounds_passed, std::move(kex_origins_map), "" @@ -5201,7 +5225,10 @@ bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const if (total) *total = m_multisig_signers.size(); if (ready) - *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())); + { + *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); + } return true; } //---------------------------------------------------------------------------------------------------- @@ -6613,7 +6640,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; rct::multisig_out msout; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, rct_config, m_multisig ? &msout : NULL); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, rct_config, m_multisig ? &msout : NULL, sd.use_view_tags); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype); // we don't test tx size, because we don't know the current limit, due to not having a blockchain, // and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway, @@ -6698,16 +6725,17 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin for (size_t i = 0; i < tx.vout.size(); ++i) { - if (tx.vout[i].target.type() != typeid(cryptonote::txout_to_key)) + crypto::public_key output_public_key; + if (!get_output_public_key(tx.vout[i], output_public_key)) continue; - const cryptonote::txout_to_key &out = boost::get<cryptonote::txout_to_key>(tx.vout[i].target); + // if this output is back to this wallet, we can calculate its key image already - if (!is_out_to_acc_precomp(m_subaddresses, out.key, derivation, additional_derivations, i, hwdev)) + if (!is_out_to_acc_precomp(m_subaddresses, output_public_key, derivation, additional_derivations, i, hwdev, get_output_view_tag(tx.vout[i]))) continue; crypto::key_image ki; cryptonote::keypair in_ephemeral; - if (generate_key_image_helper(keys, m_subaddresses, out.key, tx_pub_key, additional_tx_pub_keys, i, in_ephemeral, ki, hwdev)) - signed_txes.tx_key_images[out.key] = ki; + if (generate_key_image_helper(keys, m_subaddresses, output_public_key, tx_pub_key, additional_tx_pub_keys, i, in_ephemeral, ki, hwdev)) + signed_txes.tx_key_images[output_public_key] = ki; else MERROR("Failed to calculate key image"); } @@ -7132,7 +7160,8 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto rct::multisig_out msout = ptx.multisig_sigs.front().msout; auto sources = sd.sources; rct::RCTConfig rct_config = sd.rct_config; - bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, rct_config, &msout, false); + bool shuffle_outs = false; + bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, rct_config, &msout, shuffle_outs, sd.use_view_tags); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype); THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx), @@ -7232,17 +7261,17 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto return sign_multisig_tx_to_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const +uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, bool use_view_tags, uint64_t base_fee, uint64_t fee_quantization_mask) const { if (use_per_byte_fee) { - const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); - return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask); + const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); + return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_quantization_mask); } else { - const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); - return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); + const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); + return calculate_fee(base_fee, estimated_tx_size); } } @@ -7315,6 +7344,40 @@ uint64_t wallet2::get_base_fee() return get_dynamic_base_fee_estimate(); } //---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_base_fee(uint32_t priority) +{ + const bool use_2021_scaling = use_fork_rules(HF_VERSION_2021_SCALING, -30 * 1); + if (use_2021_scaling) + { + // clamp and map to 0..3 indices, mapping 0 (default, but should not end up here) to 0, and 1..4 to 0..3 + if (priority == 0) + priority = 1; + else if (priority > 4) + priority = 4; + --priority; + + std::vector<uint64_t> fees; + boost::optional<std::string> result = m_node_rpc_proxy.get_dynamic_base_fee_estimate_2021_scaling(FEE_ESTIMATE_GRACE_BLOCKS, fees); + if (result) + { + MERROR("Failed to determine base fee, using default"); + return FEE_PER_BYTE; + } + if (priority >= fees.size()) + { + MERROR("Failed to determine base fee for priority " << priority << ", using default"); + return FEE_PER_BYTE; + } + return fees[priority]; + } + else + { + const uint64_t base_fee = get_base_fee(); + const uint64_t fee_multiplier = get_fee_multiplier(priority); + return base_fee * fee_multiplier; + } +} +//---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_quantization_mask() { if(m_light_wallet) @@ -7346,6 +7409,8 @@ int wallet2::get_fee_algorithm() //------------------------------------------------------------------------------------------------------------------------------ uint64_t wallet2::get_min_ring_size() { + if (use_fork_rules(HF_VERSION_MIN_MIXIN_15, 0)) + return 16; if (use_fork_rules(8, 10)) return 11; if (use_fork_rules(7, 10)) @@ -7359,6 +7424,8 @@ uint64_t wallet2::get_min_ring_size() //------------------------------------------------------------------------------------------------------------------------------ uint64_t wallet2::get_max_ring_size() { + if (use_fork_rules(HF_VERSION_MIN_MIXIN_15, 0)) + return 16; if (use_fork_rules(8, 10)) return 11; return 0; @@ -7389,9 +7456,8 @@ uint32_t wallet2::adjust_priority(uint32_t priority) { // check if there's a backlog in the tx pool const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); - const uint64_t base_fee = get_base_fee(); - const uint64_t fee_multiplier = get_fee_multiplier(1); - const double fee_level = fee_multiplier * base_fee * (use_per_byte_fee ? 1 : (12/(double)13 / (double)1024)); + const uint64_t base_fee = get_base_fee(1); + const double fee_level = base_fee * (use_per_byte_fee ? 1 : (12/(double)13 / (double)1024)); const std::vector<std::pair<uint64_t, uint64_t>> blocks = estimate_backlog({std::make_pair(fee_level, fee_level)}); if (blocks.size() != 1) { @@ -8428,7 +8494,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> { size_t i = base + n; if (req.outputs[i].index == td.m_global_output_index) - if (daemon_resp.outs[i].key == boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key) + if (daemon_resp.outs[i].key == td.get_public_key()) if (daemon_resp.outs[i].mask == mask) if (daemon_resp.outs[i].unlocked) real_out_found = true; @@ -8437,7 +8503,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> "Daemon response did not include the requested real output"); // pick real out first (it will be sorted when done) - outs.back().push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask)); + outs.back().push_back(std::make_tuple(td.m_global_output_index, td.get_public_key(), mask)); // then pick outs from an existing ring, if any if (td.m_key_image_known && !td.m_key_image_partial) @@ -8528,7 +8594,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> template<typename T> void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, + bool use_view_tags) { using namespace cryptonote; // throw if attempting a transaction with no destinations @@ -8601,7 +8668,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent tx_output_entry real_oe; real_oe.first = td.m_global_output_index; - real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); + real_oe.second.dest = rct::pk2rct(td.get_public_key()); real_oe.second.mask = rct::commit(td.amount(), td.m_mask); *it_to_replace = real_oe; src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index); @@ -8639,7 +8706,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent std::vector<crypto::secret_key> additional_tx_keys; rct::multisig_out msout; LOG_PRINT_L2("constructing tx"); - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, {}, m_multisig ? &msout : NULL); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, {}, m_multisig ? &msout : NULL, use_view_tags); LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); @@ -8677,6 +8744,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.use_rct = false; ptx.construction_data.rct_config = { rct::RangeProofBorromean, 0 }; + ptx.construction_data.use_view_tags = use_view_tags; ptx.construction_data.dests = dsts; // record which subaddress indices are being used as inputs ptx.construction_data.subaddr_account = subaddr_account; @@ -8688,7 +8756,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config) + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, bool use_view_tags) { using namespace cryptonote; // throw if attempting a transaction with no destinations @@ -8873,7 +8941,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry rct::multisig_out msout; LOG_PRINT_L2("constructing tx"); auto sources_copy = sources; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, rct_config, m_multisig ? &msout : NULL); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, rct_config, m_multisig ? &msout : NULL, use_view_tags); LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype); THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); @@ -8918,7 +8986,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry LOG_PRINT_L2("Creating supplementary multisig transaction"); cryptonote::transaction ms_tx; auto sources_copy_copy = sources_copy; - bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, rct_config, &msout, false); + bool shuffle_outs = false; + bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, rct_config, &msout, shuffle_outs, use_view_tags); LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); @@ -8965,6 +9034,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry rct::RangeProofPaddedBulletproof, use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, -10) ? 4 : 3 }; + ptx.construction_data.use_view_tags = use_fork_rules(get_view_tag_fork(), 0); ptx.construction_data.dests = dsts; // record which subaddress indices are being used as inputs ptx.construction_data.subaddr_account = subaddr_account; @@ -9664,9 +9734,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp rct::RangeProofPaddedBulletproof, bulletproof_plus ? 4 : 3 }; + const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0); - const uint64_t base_fee = get_base_fee(); - const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); + const uint64_t base_fee = get_base_fee(priority); const uint64_t fee_quantization_mask = get_fee_quantization_mask(); // throw if attempting a transaction with no destinations @@ -9698,7 +9768,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // early out if we know we can't make it anyway // we could also check for being within FEE_PER_KB, but if the fee calculation // ever changes, this might be missed, so let this go through - const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus)); + const uint64_t min_fee = (base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags)); uint64_t balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) @@ -9716,11 +9786,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Candidate subaddress index for spending: " << i); // determine threshold for fractional amount - const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); + const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus, use_view_tags); + const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus, use_view_tags); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; - const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); + const uint64_t fractional_threshold = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); // gather all dust and non-dust outputs belonging to specified subaddresses size_t num_nondust_outputs = 0; @@ -9814,7 +9884,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // will get us a known fee. - uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask); + uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags, base_fee, fee_quantization_mask); preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices); if (!preferred_inputs.empty()) { @@ -9927,7 +9997,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) + while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // we can fully pay that destination LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << @@ -9945,7 +10015,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() && - estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { + estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // we can partially fill that destination LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); @@ -9983,7 +10053,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus); + const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags); try_tx = dsts.empty() || (estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); THROW_WALLET_EXCEPTION_IF(try_tx && tx.dsts.empty(), error::tx_too_big, estimated_rct_tx_weight, upper_transaction_weight_limit); } @@ -9994,7 +10064,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp pending_tx test_ptx; const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); - needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask); + needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags, base_fee, fee_quantization_mask); auto try_carving_from_partial_payment = [&](uint64_t needed_fee, uint64_t available_for_fee) { @@ -10043,12 +10113,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp tx.selected_transfers.size() << " inputs"); if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config); + test_tx, test_ptx, rct_config, use_view_tags); else transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); - needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); + needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); LOG_PRINT_L2("Made a " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); @@ -10068,12 +10138,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp while (needed_fee > test_ptx.fee) { if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config); + test_tx, test_ptx, rct_config, use_view_tags); else transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); txBlob = t_serializable_object_to_blob(test_ptx.tx); - needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); + needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); } @@ -10141,7 +10211,8 @@ skip_tx: extra, /* const std::vector<uint8_t>& extra, */ test_tx, /* OUT cryptonote::transaction& tx, */ test_ptx, /* OUT cryptonote::transaction& tx, */ - rct_config); + rct_config, + use_view_tags); /* const bool use_view_tags */ } else { transfer_selected(tx.dsts, tx.selected_transfers, @@ -10153,7 +10224,8 @@ skip_tx: detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, - test_ptx); + test_ptx, + use_view_tags); } auto txBlob = t_serializable_object_to_blob(test_ptx.tx); tx.tx = test_tx; @@ -10256,13 +10328,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); - const uint64_t base_fee = get_base_fee(); - const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); - const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); + const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0); + const uint64_t base_fee = get_base_fee(priority); + const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus, use_view_tags); + const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus, use_view_tags); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; - const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); + const uint64_t fractional_threshold = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account, false) == 0, error::wallet_internal_error, "No unlocked balance in the specified account"); @@ -10371,8 +10443,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton rct::RangeProofPaddedBulletproof, bulletproof_plus ? 4 : 3 }; - const uint64_t base_fee = get_base_fee(); - const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); + const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0); + const uint64_t base_fee = get_base_fee(priority); const uint64_t fee_quantization_mask = get_fee_quantization_mask(); LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); @@ -10398,12 +10470,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton uint64_t fee_dust_threshold; if (use_fork_rules(HF_VERSION_PER_BYTE_FEE)) { - const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus); - fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_multiplier, fee_quantization_mask); + const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags); + fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_quantization_mask); } else { - fee_dust_threshold = base_fee * fee_multiplier * (upper_transaction_weight_limit + 1023) / 1024; + fee_dust_threshold = base_fee * (upper_transaction_weight_limit + 1023) / 1024; } size_t idx = @@ -10429,7 +10501,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton // here, check if we need to sent tx and start a new one LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_weight_limit); - const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag, bulletproof_plus); + const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags); bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); if (try_tx) { @@ -10437,7 +10509,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton pending_tx test_ptx; const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); - needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask); + needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags, base_fee, fee_quantization_mask); // add N - 1 outputs for correct initial fee estimation for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i) @@ -10447,12 +10519,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton tx.selected_transfers.size() << " outputs"); if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config); + test_tx, test_ptx, rct_config, use_view_tags); else transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); - needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); + needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount; for (auto &dt: test_ptx.dests) available_for_fee += dt.amount; @@ -10484,12 +10556,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton } if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config); + test_tx, test_ptx, rct_config, use_view_tags); else transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); txBlob = t_serializable_object_to_blob(test_ptx.tx); - needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); + needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); } while (needed_fee > test_ptx.fee); @@ -10523,10 +10595,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton pending_tx test_ptx; if (use_rct) { transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra, - test_tx, test_ptx, rct_config); + test_tx, test_ptx, rct_config, use_view_tags); } else { transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, unlock_time, tx.needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); } auto txBlob = t_serializable_object_to_blob(test_ptx.tx); tx.tx = test_tx; @@ -10595,7 +10667,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_ hw::wallet_shim wallet_shim; setup_shim(&wallet_shim, this); aux_data.tx_recipients = dsts_info; - aux_data.bp_version = (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1); + aux_data.bp_version = (use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, -10) ? 4 : use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1); aux_data.hard_fork = get_current_hard_fork(); dev_cold->tx_sign(&wallet_shim, txs, exported_txs, aux_data); tx_device_aux = aux_data.tx_device_aux; @@ -10816,7 +10888,7 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions() const bool hf1_rules = use_fork_rules(2, 10); // first hard fork has version 2 tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD); - const uint64_t base_fee = get_base_fee(); + const uint64_t base_fee = get_base_fee(1); // may throw std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(); @@ -11065,13 +11137,12 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string // derive the real output keypair const transfer_details& in_td = m_transfers[found->second]; - const txout_to_key* const in_tx_out_pkey = boost::get<txout_to_key>(std::addressof(in_td.m_tx.vout[in_td.m_internal_output_index].target)); - THROW_WALLET_EXCEPTION_IF(in_tx_out_pkey == nullptr, error::wallet_internal_error, "Output is not txout_to_key"); + crypto::public_key in_tx_out_pkey = in_td.get_public_key(); const crypto::public_key in_tx_pub_key = get_tx_pub_key_from_extra(in_td.m_tx, in_td.m_pk_index); const std::vector<crypto::public_key> in_additionakl_tx_pub_keys = get_additional_tx_pub_keys_from_extra(in_td.m_tx); keypair in_ephemeral; crypto::key_image in_img; - THROW_WALLET_EXCEPTION_IF(!generate_key_image_helper(m_account.get_keys(), m_subaddresses, in_tx_out_pkey->key, in_tx_pub_key, in_additionakl_tx_pub_keys, in_td.m_internal_output_index, in_ephemeral, in_img, m_account.get_device()), + THROW_WALLET_EXCEPTION_IF(!generate_key_image_helper(m_account.get_keys(), m_subaddresses, in_tx_out_pkey, in_tx_pub_key, in_additionakl_tx_pub_keys, in_td.m_internal_output_index, in_ephemeral, in_img, m_account.get_device()), error::wallet_internal_error, "failed to generate key image"); THROW_WALLET_EXCEPTION_IF(in_key->k_image != in_img, error::wallet_internal_error, "key image mismatch"); @@ -11270,24 +11341,12 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt for (size_t n = 0; n < tx.vout.size(); ++n) { - const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[n].target)); - if (!out_key) + crypto::public_key output_public_key; + if (!get_output_public_key(tx.vout[n], output_public_key)) continue; - crypto::public_key derived_out_key; - bool r = crypto::derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); - bool found = out_key->key == derived_out_key; - crypto::key_derivation found_derivation = derivation; - if (!found && !additional_derivations.empty()) - { - r = crypto::derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); - found = out_key->key == derived_out_key; - found_derivation = additional_derivations[n]; - } - - if (found) + crypto::key_derivation found_derivation; + if (is_out_to_acc(address, output_public_key, derivation, additional_derivations, n, get_output_view_tag(tx.vout[n]), found_derivation)) { uint64_t amount; if (tx.version == 1 || tx.rct_signatures.type == rct::RCTTypeNull) @@ -11381,6 +11440,42 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de } } +bool wallet2::is_out_to_acc(const cryptonote::account_public_address &address, const crypto::public_key& out_key, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const size_t output_index, const boost::optional<crypto::view_tag> &view_tag_opt, crypto::key_derivation &found_derivation) const +{ + crypto::public_key derived_out_key; + bool found = false; + bool r; + // first run quick check if output has matching view tag, otherwise output should not belong to account + if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + { + // if view tag match, run slower check deriving output pub key and comparing to expected + r = crypto::derive_public_key(derivation, output_index, address.m_spend_public_key, derived_out_key); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); + if (out_key == derived_out_key) + { + found = true; + found_derivation = derivation; + } + } + + if (!found && !additional_derivations.empty()) + { + const crypto::key_derivation &additional_derivation = additional_derivations[output_index]; + if (out_can_be_to_acc(view_tag_opt, additional_derivation, output_index)) + { + r = crypto::derive_public_key(additional_derivation, output_index, address.m_spend_public_key, derived_out_key); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); + if (out_key == derived_out_key) + { + found = true; + found_derivation = additional_derivation; + } + } + } + + return found; +} + std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message) { // fetch tx pubkey from the daemon @@ -11917,8 +12012,8 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr THROW_WALLET_EXCEPTION_IF(proof.index_in_tx >= tx.vout.size(), error::wallet_internal_error, "index_in_tx is out of bound"); - const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[proof.index_in_tx].target)); - THROW_WALLET_EXCEPTION_IF(!out_key, error::wallet_internal_error, "Output key wasn't found") + crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(!get_output_public_key(tx.vout[proof.index_in_tx], output_public_key), error::wallet_internal_error, "Output key wasn't found"); // get tx pub key const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); @@ -11933,7 +12028,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr return false; // check signature for key image - const std::vector<const crypto::public_key*> pubs = { &out_key->key }; + const std::vector<const crypto::public_key*> pubs = { &output_public_key }; ok = crypto::check_ring_signature(prefix_hash, proof.key_image, &pubs[0], 1, &proof.key_image_sig); if (!ok) return false; @@ -11942,7 +12037,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr crypto::key_derivation derivation; THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); crypto::public_key subaddr_spendkey; - crypto::derive_subaddress_public_key(out_key->key, derivation, proof.index_in_tx, subaddr_spendkey); + crypto::derive_subaddress_public_key(output_public_key, derivation, proof.index_in_tx, subaddr_spendkey); THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(subaddr_spendkey) == 0, error::wallet_internal_error, "The address doesn't seem to have received the fund"); @@ -12396,11 +12491,7 @@ std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>> const transfer_details &td = m_transfers[n]; // get ephemeral public key - const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; - THROW_WALLET_EXCEPTION_IF(out.target.type() != typeid(txout_to_key), error::wallet_internal_error, - "Output is not txout_to_key"); - const cryptonote::txout_to_key &o = boost::get<const cryptonote::txout_to_key>(out.target); - const crypto::public_key pkey = o.key; + const crypto::public_key pkey = td.get_public_key(); // get tx pub key std::vector<tx_extra_field> tx_extra_fields; @@ -12517,11 +12608,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag const crypto::signature &signature = signed_key_images[n].second; // get ephemeral public key - const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; - THROW_WALLET_EXCEPTION_IF(out.target.type() != typeid(txout_to_key), error::wallet_internal_error, - "Non txout_to_key output found"); - const cryptonote::txout_to_key &o = boost::get<cryptonote::txout_to_key>(out.target); - const crypto::public_key pkey = o.key; + const crypto::public_key pkey = td.get_public_key(); if (!td.m_key_image_known || !(key_image == td.m_key_image)) { @@ -12977,9 +13064,7 @@ 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"); - THROW_WALLET_EXCEPTION_IF(td.m_tx.vout[td.m_internal_output_index].target.type() != typeid(cryptonote::txout_to_key), - error::wallet_internal_error, "Unsupported output type"); - const crypto::public_key& out_key = boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; + crypto::public_key out_key = td.get_public_key(); bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, 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)) @@ -14205,8 +14290,9 @@ std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, i const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); - size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); - uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); + const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0); + size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); + uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus, use_view_tags); return std::make_pair(size, weight); } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f5da348a9..660e6a14b 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -347,7 +347,12 @@ private: bool is_rct() const { return m_rct; } uint64_t amount() const { return m_amount; } - const crypto::public_key &get_public_key() const { return boost::get<const cryptonote::txout_to_key>(m_tx.vout[m_internal_output_index].target).key; } + const crypto::public_key get_public_key() const { + crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(!get_output_public_key(m_tx.vout[m_internal_output_index], output_public_key), + error::wallet_internal_error, "Unable to get output public key from output"); + return output_public_key; + }; BEGIN_SERIALIZE_OBJECT() FIELD(m_block_height) @@ -529,10 +534,21 @@ private: uint64_t unlock_time; bool use_rct; rct::RCTConfig rct_config; + bool use_view_tags; std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer std::set<uint32_t> subaddr_indices; // set of address indices used as inputs in this transfer + enum construction_flags_ : uint8_t + { + _use_rct = 1 << 0, // 00000001 + _use_view_tags = 1 << 1 // 00000010 + // next flag = 1 << 2 // 00000100 + // ... + // final flag = 1 << 7 // 10000000 + }; + uint8_t construction_flags; + BEGIN_SERIALIZE_OBJECT() FIELD(sources) FIELD(change_dts) @@ -540,7 +556,26 @@ private: FIELD(selected_transfers) FIELD(extra) FIELD(unlock_time) - FIELD(use_rct) + + // converted `use_rct` field into construction_flags when view tags + // were introduced to maintain backwards compatibility + if (!typename Archive<W>::is_saving()) + { + FIELD_N("use_rct", construction_flags) + use_rct = (construction_flags & _use_rct) > 0; + use_view_tags = (construction_flags & _use_view_tags) > 0; + } + else + { + construction_flags = 0; + if (use_rct) + construction_flags ^= _use_rct; + if (use_view_tags) + construction_flags ^= _use_view_tags; + + FIELD_N("use_rct", construction_flags) + } + FIELD(rct_config) FIELD(dests) FIELD(subaddr_account) @@ -967,10 +1002,10 @@ private: template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, const bool use_view_tags); void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config); + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, const bool use_view_tags); void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); @@ -1090,9 +1125,7 @@ private: for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details &td = m_transfers[i]; - const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; - const cryptonote::txout_to_key &o = boost::get<const cryptonote::txout_to_key>(out.target); - m_pub_keys.emplace(o.key, i); + m_pub_keys.emplace(td.get_public_key(), i); } return; } @@ -1271,6 +1304,7 @@ private: void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); void check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); void check_tx_key_helper(const cryptonote::transaction &tx, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received) const; + bool is_out_to_acc(const cryptonote::account_public_address &address, const crypto::public_key& out_key, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const size_t output_index, const boost::optional<crypto::view_tag> &view_tag_opt, crypto::key_derivation &found_derivation) const; std::string get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message); std::string get_tx_proof(const cryptonote::transaction &tx, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message) const; bool check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations); @@ -1427,8 +1461,9 @@ private: std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels); std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees); - uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const; + uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, bool use_view_tags, uint64_t base_fee, uint64_t fee_quantization_mask) const; uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1); + uint64_t get_base_fee(uint32_t priority); uint64_t get_base_fee(); uint64_t get_fee_quantization_mask(); uint64_t get_min_ring_size(); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 543caac1b..57baf428f 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -4134,7 +4134,8 @@ namespace tools try { res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info); - if (res.multisig_info.empty()) + m_wallet->multisig(&ready); + if (ready) { res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); } |