From 0f8a3eded6490ff54885744527042e6c2a4176dd Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 5 Nov 2021 17:51:54 +0000 Subject: [PATCH 1/8] wallet2: speedup large tx construction: cache public key validity 5.9 second -> 5.2 seconds on a test case --- src/wallet/wallet2.cpp | 82 +++++++++++++++++++++++------------------- src/wallet/wallet2.h | 14 ++++---- 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 39de21899..77ea1170d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8339,7 +8339,7 @@ bool wallet2::is_keys_file_locked() const return m_keys_file_locker->locked(); } -bool wallet2::tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const +bool wallet2::tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set &valid_public_keys_cache) const { if (!unlocked) // don't add locked outs return false; @@ -8350,16 +8350,18 @@ bool wallet2::tx_add_fake_output(std::vector valid_public_keys_cache; for(size_t idx: selected_transfers) { // Create new index @@ -8455,7 +8458,7 @@ void wallet2::light_wallet_get_outs(std::vector, size_t> outs_unique(const std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count, bool rct) +void wallet2::get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count, bool rct, std::unordered_set &valid_public_keys_cache) { std::vector rct_offsets; for (size_t attempts = 3; attempts > 0; --attempts) { - get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets); + get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets, valid_public_keys_cache); if (!rct) return; @@ -8519,7 +8522,7 @@ void wallet2::get_outs(std::vector> THROW_WALLET_EXCEPTION(error::wallet_internal_error, tr("Transaction sanity check failed")); } -void wallet2::get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count, std::vector &rct_offsets) +void wallet2::get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count, std::vector &rct_offsets, std::unordered_set &valid_public_keys_cache) { LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); @@ -9034,7 +9037,7 @@ void wallet2::get_outs(std::vector> if (req.outputs[i].index == out) { LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key << " (from existing ring)"); - tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked); + tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked, valid_public_keys_cache); found = true; break; } @@ -9059,7 +9062,7 @@ void wallet2::get_outs(std::vector> { size_t i = base + order[o]; LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key); - tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked); + tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked, valid_public_keys_cache); } if (outs.back().size() < fake_outputs_count + 1) { @@ -9103,8 +9106,9 @@ void wallet2::get_outs(std::vector> template void wallet2::transfer_selected(const std::vector& dsts, const std::vector& selected_transfers, size_t fake_outputs_count, - std::vector> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) + std::vector> &outs, std::unordered_set &valid_public_keys_cache, + uint64_t unlock_time, uint64_t fee, const std::vector& 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 @@ -9140,7 +9144,7 @@ void wallet2::transfer_selected(const std::vector dsts, const std::vector& selected_transfers, size_t fake_outputs_count, - std::vector> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config) + std::vector> &outs, std::unordered_set &valid_public_keys_cache, + uint64_t unlock_time, uint64_t fee, const std::vector& 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 @@ -9357,7 +9361,7 @@ void wallet2::transfer_selected_rct(std::vector wallet2::create_transactions_2(std::vector valid_public_keys_cache; const uint64_t base_fee = get_base_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); @@ -10585,11 +10591,11 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector 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); + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + 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); + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + 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); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << @@ -10697,6 +10703,7 @@ skip_tx: tx.selected_transfers, /* const std::list selected_transfers */ fake_outs_count, /* CONST size_t fake_outputs_count, */ tx.outs, /* MOD std::vector> &outs, */ + valid_public_keys_cache, unlock_time, /* CONST uint64_t unlock_time, */ tx.needed_fee, /* CONST uint64_t fee, */ extra, /* const std::vector& extra, */ @@ -10708,6 +10715,7 @@ skip_tx: tx.selected_transfers, fake_outs_count, tx.outs, + valid_public_keys_cache, unlock_time, tx.needed_fee, extra, @@ -10826,7 +10834,8 @@ std::vector wallet2::create_transactions_all(uint64_t below const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); 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); + std::unordered_set valid_public_keys_cache; THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account, false) == 0, error::wallet_internal_error, "No unlocked balance in the specified account"); @@ -10908,6 +10917,7 @@ std::vector wallet2::create_transactions_from(const crypton hw::device &hwdev = m_account.get_device(); boost::unique_lock hwdev_lock (hwdev); hw::reset_mode rst(hwdev); + std::unordered_set valid_public_keys_cache; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { @@ -11010,11 +11020,11 @@ std::vector wallet2::create_transactions_from(const crypton LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << 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); + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + 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); + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + 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); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount; @@ -11047,11 +11057,11 @@ std::vector wallet2::create_transactions_from(const crypton dt.amount = dt_amount + dt_residue; } if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config); + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + 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); + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + 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); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << @@ -11086,11 +11096,11 @@ std::vector wallet2::create_transactions_from(const crypton cryptonote::transaction test_tx; 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); + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.needed_fee, extra, + 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); + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.needed_fee, extra, + 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; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f642679d4..02acebac8 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -951,11 +951,11 @@ private: uint64_t unlocked_balance_all(bool strict, uint64_t *blocks_to_unlock = NULL, uint64_t *time_to_unlock = NULL); template void transfer_selected(const std::vector& dsts, const std::vector& selected_transfers, size_t fake_outputs_count, - std::vector> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); + std::vector> &outs, std::unordered_set &valid_public_keys_cache, + uint64_t unlock_time, uint64_t fee, const std::vector& 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 dsts, const std::vector& selected_transfers, size_t fake_outputs_count, - std::vector> &outs, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config); + std::vector> &outs, std::unordered_set &valid_public_keys_cache, + uint64_t unlock_time, uint64_t fee, const std::vector& 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& ptx_vector); @@ -1633,9 +1633,9 @@ private: void set_unspent(size_t idx); bool is_spent(const transfer_details &td, bool strict = true) const; bool is_spent(size_t idx, bool strict = true) const; - void get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count, bool rct); - void get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count, std::vector &rct_offsets); - bool tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; + void get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count, bool rct, std::unordered_set &valid_public_keys_cache); + void get_outs(std::vector> &outs, const std::vector &selected_transfers, size_t fake_outputs_count, std::vector &rct_offsets, std::unordered_set &valid_public_keys_cache); + bool tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set &valid_public_keys_cache) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector &unused_transfers_indices, const std::vector &unused_dust_indices) const; std::vector get_only_rct(const std::vector &unused_dust_indices, const std::vector &unused_transfers_indices) const; void scan_output(const cryptonote::transaction &tx, bool miner_tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map &tx_money_got_in_outs, std::vector &outs, bool pool); From 7cdb8244ee8712a6fe389fb2193791ce87425ead Mon Sep 17 00:00:00 2001 From: Crypto City Date: Fri, 5 Nov 2021 17:47:37 +0000 Subject: [PATCH 2/8] wallet2: speedup large tx construction: batch ringdb updates 5.2 seconds -> 4.1 seconds on a test case --- src/wallet/ringdb.cpp | 17 ++++++++++++++--- src/wallet/ringdb.h | 1 + src/wallet/wallet2.cpp | 16 ++++++++++++++-- src/wallet/wallet2.h | 1 + 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index dfeb987ca..93c11d32c 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -389,20 +389,24 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return true; } -bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector &outs, bool relative) +bool ringdb::set_rings(const crypto::chacha_key &chacha_key, const std::vector>> &rings, bool relative) { MDB_txn *txn; int dbr; bool tx_active = false; - dbr = resize_env(env, filename.c_str(), outs.size() * 64); + size_t n_outs = 0; + for (const auto &e: rings) + n_outs += e.second.size(); + dbr = resize_env(env, filename.c_str(), n_outs * 64); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); dbr = mdb_txn_begin(env, NULL, 0, &txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; - store_relative_ring(txn, dbi_rings, key_image, relative ? outs : cryptonote::absolute_output_offsets_to_relative(outs), chacha_key); + for (const auto &e: rings) + store_relative_ring(txn, dbi_rings, e.first, relative ? e.second : cryptonote::absolute_output_offsets_to_relative(e.second), chacha_key); dbr = mdb_txn_commit(txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn setting ring to database: " + std::string(mdb_strerror(dbr))); @@ -410,6 +414,13 @@ bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return true; } +bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector &outs, bool relative) +{ + std::vector>> rings; + rings.push_back(std::make_pair(key_image, outs)); + return set_rings(chacha_key, rings, relative); +} + bool ringdb::blackball_worker(const std::vector> &outputs, int op) { MDB_txn *txn; diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 9c7e624bc..317942676 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -49,6 +49,7 @@ namespace tools bool remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs); bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector &outs, bool relative); + bool set_rings(const crypto::chacha_key &chacha_key, const std::vector>> &rings, bool relative); bool blackball(const std::pair &output); bool blackball(const std::vector> &outputs); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 77ea1170d..b483ab216 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8165,6 +8165,15 @@ bool wallet2::set_ring(const crypto::key_image &key_image, const std::vector>> &rings, bool relative) +{ + if (!m_ringdb) + return false; + + try { return m_ringdb->set_rings(get_ringdb_key(), rings, relative); } + catch (const std::exception &e) { return false; } +} + bool wallet2::unset_ring(const std::vector &key_images) { if (!m_ringdb) @@ -9090,6 +9099,8 @@ void wallet2::get_outs(std::vector> } // save those outs in the ringdb for reuse + std::vector>> rings; + rings.reserve(selected_transfers.size()); for (size_t i = 0; i < selected_transfers.size(); ++i) { const size_t idx = selected_transfers[i]; @@ -9099,9 +9110,10 @@ void wallet2::get_outs(std::vector> ring.reserve(outs[i].size()); for (const auto &e: outs[i]) ring.push_back(std::get<0>(e)); - if (!set_ring(td.m_key_image, ring, false)) - MERROR("Failed to set ring for " << td.m_key_image); + rings.push_back(std::make_pair(td.m_key_image, std::move(ring))); } + if (!set_rings(rings, false)) + MERROR("Failed to set rings"); } template diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 02acebac8..8401bf65a 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1506,6 +1506,7 @@ private: bool get_ring(const crypto::key_image &key_image, std::vector &outs); bool get_rings(const crypto::hash &txid, std::vector>> &outs); bool set_ring(const crypto::key_image &key_image, const std::vector &outs, bool relative); + bool set_rings(const std::vector>> &rings, bool relative); bool unset_ring(const std::vector &key_images); bool unset_ring(const crypto::hash &txid); bool find_and_save_rings(bool force = true); From 4a60dfbb3e75f7c692e270eeb9eea5d758b5e92c Mon Sep 17 00:00:00 2001 From: Crypto City Date: Fri, 5 Nov 2021 18:10:22 +0000 Subject: [PATCH 3/8] wallet2: speedup large tx construction: no pointless clsag generation 4.1 seconds -> 3.3 seconds on a test case --- src/ringct/rctSigs.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 7e0b33fd2..278da484f 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -79,6 +79,7 @@ namespace return rct::Bulletproof{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I), I, I, I}; } + rct::BulletproofPlus make_dummy_bulletproof_plus(const std::vector &outamounts, rct::keyV &C, rct::keyV &masks) { const size_t n_outs = outamounts.size(); @@ -109,6 +110,13 @@ namespace return rct::BulletproofPlus{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I)}; } + + rct::clsag make_dummy_clsag(size_t ring_size) + { + const rct::key I = rct::identity(); + const size_t n_scalars = ring_size; + return rct::clsag{rct::keyV(n_scalars, I), I, I, I}; + } } namespace rct { @@ -1400,7 +1408,10 @@ namespace rct { { if (is_rct_clsag(rv.type)) { - rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev); + if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE) + rv.p.CLSAGs[i] = make_dummy_clsag(rv.mixRing[i].size()); + else + rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev); } else { From 940811465fcd51238b0402ab04e6a7286d182261 Mon Sep 17 00:00:00 2001 From: Crypto City Date: Fri, 5 Nov 2021 19:18:10 +0000 Subject: [PATCH 4/8] wallet2: speedup large tx construction: batch ringdb lookups 3.3 seconds -> 2.8 seconds on a test case --- src/wallet/ringdb.cpp | 22 +++++++++++++++++++++- src/wallet/ringdb.h | 1 + src/wallet/wallet2.cpp | 39 +++++++++++++++++++++++++++++++++++---- src/wallet/wallet2.h | 1 + 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 93c11d32c..6a48d8117 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -346,12 +346,15 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote return remove_rings(chacha_key, key_images); } -bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs) +bool ringdb::get_rings(const crypto::chacha_key &chacha_key, const std::vector &key_images, std::vector> &all_outs) { MDB_txn *txn; int dbr; bool tx_active = false; + all_outs.clear(); + all_outs.reserve(key_images.size()); + dbr = resize_env(env, filename.c_str(), 0); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); dbr = mdb_txn_begin(env, NULL, 0, &txn); @@ -359,6 +362,10 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; + for (size_t i = 0; i < key_images.size(); ++i) + { + const crypto::key_image &key_image = key_images[i]; + MDB_val key, data; std::string key_ciphertext = encrypt(key_image, chacha_key, 0); key.mv_data = (void*)key_ciphertext.data(); @@ -369,6 +376,7 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return false; THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); + std::vector outs; bool try_v0 = false; std::string data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key, 1); try { outs = decompress_ring(data_plaintext, V1TAG); if (outs.empty()) try_v0 = true; } @@ -382,6 +390,9 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im MDEBUG("Relative: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); outs = cryptonote::relative_output_offsets_to_absolute(outs); MDEBUG("Absolute: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); + all_outs.push_back(std::move(outs)); + + } dbr = mdb_txn_commit(txn); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn getting ring from database: " + std::string(mdb_strerror(dbr))); @@ -389,6 +400,15 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return true; } +bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs) +{ + std::vector> all_outs; + if (!get_rings(chacha_key, std::vector(1, key_image), all_outs)) + return false; + outs = std::move(all_outs.front()); + return true; +} + bool ringdb::set_rings(const crypto::chacha_key &chacha_key, const std::vector>> &rings, bool relative) { MDB_txn *txn; diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 317942676..02a4f8b48 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -48,6 +48,7 @@ namespace tools bool remove_rings(const crypto::chacha_key &chacha_key, const std::vector &key_images); bool remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector &outs); + bool get_rings(const crypto::chacha_key &chacha_key, const std::vector &key_images, std::vector> &all_outs); bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector &outs, bool relative); bool set_rings(const crypto::chacha_key &chacha_key, const std::vector>> &rings, bool relative); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b483ab216..4b6420b92 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8127,6 +8127,14 @@ bool wallet2::get_ring(const crypto::chacha_key &key, const crypto::key_image &k catch (const std::exception &e) { return false; } } +bool wallet2::get_rings(const crypto::chacha_key &key, const std::vector &key_images, std::vector> &outs) +{ + if (!m_ringdb) + return false; + try { return m_ringdb->get_rings(key, key_images, outs); } + catch (const std::exception &e) { return false; } +} + bool wallet2::get_rings(const crypto::hash &txid, std::vector>> &outs) { for (auto i: m_confirmed_txs) @@ -8648,6 +8656,25 @@ void wallet2::get_outs(std::vector> } } + std::vector ring_key_images; + ring_key_images.reserve(selected_transfers.size()); + std::unordered_map> existing_rings; + for(size_t idx: selected_transfers) + { + const transfer_details &td = m_transfers[idx]; + if (td.m_key_image_known && !td.m_key_image_partial) + ring_key_images.push_back(td.m_key_image); + } + if (!ring_key_images.empty()) + { + std::vector> all_outs; + if (get_rings(get_ringdb_key(), ring_key_images, all_outs)) + { + for (size_t i = 0; i < ring_key_images.size(); ++i) + existing_rings[ring_key_images[i]] = std::move(all_outs[i]); + } + } + // we ask for more, to have spares if some outputs are still locked size_t base_requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1); LOG_PRINT_L2("base_requested_outputs_count: " << base_requested_outputs_count); @@ -8770,9 +8797,12 @@ void wallet2::get_outs(std::vector> // if we have a known ring, use it if (td.m_key_image_known && !td.m_key_image_partial) { - std::vector ring; - if (get_ring(get_ringdb_key(), td.m_key_image, ring)) + + const auto it = existing_rings.find(td.m_key_image); + const bool has_ring = it != existing_rings.end(); + if (has_ring) { + const std::vector &ring = it->second; MINFO("This output has a known ring, reusing (size " << ring.size() << ")"); THROW_WALLET_EXCEPTION_IF(ring.size() > fake_outputs_count + 1, error::wallet_internal_error, "An output in this transaction was previously spent on another chain with ring size " + @@ -9030,9 +9060,10 @@ void wallet2::get_outs(std::vector> // then pick outs from an existing ring, if any if (td.m_key_image_known && !td.m_key_image_partial) { - std::vector ring; - if (get_ring(get_ringdb_key(), td.m_key_image, ring)) + const auto it = existing_rings.find(td.m_key_image); + if (it != existing_rings.end()) { + const std::vector &ring = it->second; for (uint64_t out: ring) { if (out < num_outs) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 8401bf65a..6f0276ea1 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1505,6 +1505,7 @@ private: const std::string get_ring_database() const { return m_ring_database; } bool get_ring(const crypto::key_image &key_image, std::vector &outs); bool get_rings(const crypto::hash &txid, std::vector>> &outs); + bool get_rings(const crypto::chacha_key &key, const std::vector &key_images, std::vector> &outs); bool set_ring(const crypto::key_image &key_image, const std::vector &outs, bool relative); bool set_rings(const std::vector>> &rings, bool relative); bool unset_ring(const std::vector &key_images); From 2eeb3fc1bf7b61edac140d33ff445323fe70fef8 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 5 Nov 2021 19:57:49 +0000 Subject: [PATCH 5/8] wallet2: speedup large tx construction: reserve vector memory 2.8 seconds -> 2.6 seconds on a test case --- src/wallet/wallet2.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 4b6420b92..98547d5b0 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8583,6 +8583,7 @@ void wallet2::get_outs(std::vector> cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); // request histogram for all outputs, except 0 if we have the rct distribution + req_t.amounts.reserve(selected_transfers.size()); for(size_t idx: selected_transfers) if (!m_transfers[idx].is_rct() || !has_rct_distribution) req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); @@ -8610,6 +8611,7 @@ void wallet2::get_outs(std::vector> { cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response resp_t = AUTO_VAL_INIT(resp_t); + req_t.amounts.reserve(req_t.amounts.size() + selected_transfers.size()); for(size_t idx: selected_transfers) req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); std::sort(req_t.amounts.begin(), req_t.amounts.end()); @@ -8688,6 +8690,8 @@ void wallet2::get_outs(std::vector> gamma.reset(new gamma_picker(rct_offsets)); size_t num_selected_transfers = 0; + req.outputs.reserve(selected_transfers.size() * (base_requested_outputs_count + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW)); + daemon_resp.outs.reserve(selected_transfers.size() * (base_requested_outputs_count + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW)); for(size_t idx: selected_transfers) { ++num_selected_transfers; From dc3981618d2db3769be64e56725f231908a03d39 Mon Sep 17 00:00:00 2001 From: wowario Date: Thu, 5 Jan 2023 21:52:58 +0300 Subject: [PATCH 6/8] remove use_view_tags, revert fractional_threshold --- src/wallet/wallet2.cpp | 28 +++++++++++++--------------- src/wallet/wallet2.h | 4 ++-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 98547d5b0..d78a9af67 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -9154,8 +9154,7 @@ void wallet2::get_outs(std::vector> template void wallet2::transfer_selected(const std::vector& dsts, const std::vector& selected_transfers, size_t fake_outputs_count, std::vector> &outs, std::unordered_set &valid_public_keys_cache, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, - bool use_view_tags) + uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) { using namespace cryptonote; // throw if attempting a transaction with no destinations @@ -9315,7 +9314,7 @@ void wallet2::transfer_selected(const std::vector dsts, const std::vector& selected_transfers, size_t fake_outputs_count, std::vector> &outs, std::unordered_set &valid_public_keys_cache, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, bool use_view_tags) + uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config) { using namespace cryptonote; // throw if attempting a transaction with no destinations @@ -10291,7 +10290,6 @@ std::vector wallet2::create_transactions_2(std::vector valid_public_keys_cache; const uint64_t base_fee = get_base_fee(); @@ -10639,10 +10637,10 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector test_ptx.fee) { if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config, use_view_tags); + test_tx, test_ptx, rct_config); else transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); 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); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << @@ -10881,7 +10879,7 @@ std::vector wallet2::create_transactions_all(uint64_t below const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); 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 = (base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); + const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); std::unordered_set valid_public_keys_cache; THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account, false) == 0, error::wallet_internal_error, "No unlocked balance in the specified account"); @@ -11068,10 +11066,10 @@ std::vector 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, valid_public_keys_cache, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config, use_view_tags); + test_tx, test_ptx, rct_config); else transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount; @@ -11105,10 +11103,10 @@ std::vector wallet2::create_transactions_from(const crypton } if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, - test_tx, test_ptx, rct_config, use_view_tags); + test_tx, test_ptx, rct_config); else transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); 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); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << @@ -11144,10 +11142,10 @@ std::vector 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, valid_public_keys_cache, unlock_time, tx.needed_fee, extra, - test_tx, test_ptx, rct_config, use_view_tags); + test_tx, test_ptx, rct_config); } else { transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); } auto txBlob = t_serializable_object_to_blob(test_ptx.tx); tx.tx = test_tx; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 6f0276ea1..72c8e8545 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -952,10 +952,10 @@ private: template void transfer_selected(const std::vector& dsts, const std::vector& selected_transfers, size_t fake_outputs_count, std::vector> &outs, std::unordered_set &valid_public_keys_cache, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, const bool use_view_tags); + uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); void transfer_selected_rct(std::vector dsts, const std::vector& selected_transfers, size_t fake_outputs_count, std::vector> &outs, std::unordered_set &valid_public_keys_cache, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, const bool use_view_tags); + uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config); void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector& ptx_vector); From 35c9cf9c88654c1f1f6e9b50e9b141248b8145f2 Mon Sep 17 00:00:00 2001 From: wowario Date: Thu, 5 Jan 2023 23:42:52 +0300 Subject: [PATCH 7/8] bump version --- src/version.cpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.cpp.in b/src/version.cpp.in index 952b11921..6ae2c4939 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" -#define DEF_MONERO_VERSION "0.10.1.0" +#define DEF_MONERO_VERSION "0.10.2.0" #define DEF_MONERO_RELEASE_NAME "Junkie Jeff" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG #define DEF_MONERO_VERSION_IS_RELEASE @VERSION_IS_RELEASE@ From 23d1a8d68e89346379a882cc284688228cf8e3d5 Mon Sep 17 00:00:00 2001 From: wowario Date: Fri, 6 Jan 2023 01:09:52 +0300 Subject: [PATCH 8/8] update checkpoints --- src/blocks/checkpoints.dat | Bin 43524 -> 61124 bytes src/checkpoints/checkpoints.cpp | 1 + src/cryptonote_core/blockchain.cpp | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat index 1963646d317c650f6b1c87e1bfedbbd4aa4a7479..cc7272f3ac532a1942a591434154e31b9b374b7a 100644 GIT binary patch delta 17752 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 delta 13 UcmX?dm$~H%6VnQ&jZ93d04S;j3IG5A diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index cbe0888c6..1ce694813 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -224,6 +224,7 @@ namespace cryptonote ADD_CHECKPOINT2(332100, "d32c409058c1eceb9a105190c7a5f480b2d6f49f318b18652b49ae971c710124", "0x7c538441cca36"); ADD_CHECKPOINT2(334000, "17d3b15f8e1a73e1c61335ee7979e9e3d211b9055e8a7fb2481e5f49a51b1c22", "0x7ddd5a79d69c4"); ADD_CHECKPOINT2(348500, "2d43a157f369e2aa26a329b56456142ecd1361f5808c688d97112a2e3bbd23f4", "0x90889ed877ada"); + ADD_CHECKPOINT2(489400, "b14f49eae77398117ea93435676100d8b655a804689f73a5a4d0d5e71160d603", "0x1123c39bb52f7e"); return true; } diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 8b246a706..9d117ec09 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -5541,7 +5541,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "02777e7737e1a325558f28172d5041ed7b6e7026a0d19f3cd128966cad89299d"; +static const char expected_block_hashes_hash[] = "cdb3d018fc4c2505619423a24b2d694348103df9af9a3f4f0cc203f4e21363bd"; void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints) { if (get_checkpoints == nullptr || !m_fast_sync)